window.jsPsych = (function() {
var core = {};
core.version = function() { return "6.3.0" };
//
// private variables
//
// options
var opts = {};
// experiment timeline
var timeline;
// flow control
var global_trial_index = 0;
var current_trial = {};
var current_trial_finished = false;
// target DOM element
var DOM_container;
var DOM_target;
// time that the experiment began
var exp_start_time;
// is the experiment paused?
var paused = false;
var waiting = false;
// done loading?
var loaded = false;
var loadfail = false;
// is the page retrieved directly via file:// protocol (true) or hosted on a server (false)?
var file_protocol = false;
// storing a single webaudio context to prevent problems with multiple inits
// of jsPsych
core.webaudio_context = null;
// temporary patch for Safari
if (typeof window !== 'undefined' && window.hasOwnProperty('webkitAudioContext') && !window.hasOwnProperty('AudioContext')) {
window.AudioContext = webkitAudioContext;
}
// end patch
core.webaudio_context = (typeof window !== 'undefined' && typeof window.AudioContext !== 'undefined') ? new AudioContext() : null;
// enumerated variables for special parameter types
core.ALL_KEYS = 'allkeys';
core.NO_KEYS = 'none';
//
// public methods
//
core.init = function(options) {
function init() {
if(typeof options.timeline === 'undefined'){
console.error('No timeline declared in jsPsych.init. Cannot start experiment.')
}
if(options.timeline.length == 0){
console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.')
}
// reset variables
timeline = null;
global_trial_index = 0;
current_trial = {};
current_trial_finished = false;
paused = false;
waiting = false;
loaded = false;
loadfail = false;
file_protocol = false;
jsPsych.data.reset();
var defaults = {
'display_element': undefined,
'on_finish': function(data) {
return undefined;
},
'on_trial_start': function(trial) {
return undefined;
},
'on_trial_finish': function() {
return undefined;
},
'on_data_update': function(data) {
return undefined;
},
'on_interaction_data_update': function(data){
return undefined;
},
'on_close': function(){
return undefined;
},
'use_webaudio': true,
'exclusions': {},
'show_progress_bar': false,
'message_progress_bar': 'Completion Progress',
'auto_update_progress_bar': true,
'default_iti': 0,
'minimum_valid_rt': 0,
'experiment_width': null,
'override_safe_mode': false,
'case_sensitive_responses': false,
'extensions': []
};
// detect whether page is running in browser as a local file, and if so, disable web audio and video preloading to prevent CORS issues
if (window.location.protocol == 'file:' && (options.override_safe_mode === false || typeof options.override_safe_mode == 'undefined')) {
options.use_webaudio = false;
file_protocol = true;
console.warn("jsPsych detected that it is running via the file:// protocol and not on a web server. "+
"To prevent issues with cross-origin requests, Web Audio and video preloading have been disabled. "+
"If you would like to override this setting, you can set 'override_safe_mode' to 'true' in jsPsych.init. "+
"For more information, see: https://www.jspsych.org/overview/running-experiments");
}
// override default options if user specifies an option
opts = Object.assign({}, defaults, options);
// set DOM element where jsPsych will render content
// if undefined, then jsPsych will use the
tag and the entire page
if(typeof opts.display_element == 'undefined'){
// check if there is a body element on the page
var body = document.querySelector('body');
if (body === null) {
document.documentElement.appendChild(document.createElement('body'));
}
// using the full page, so we need the HTML element to
// have 100% height, and body to be full width and height with
// no margin
document.querySelector('html').style.height = '100%';
document.querySelector('body').style.margin = '0px';
document.querySelector('body').style.height = '100%';
document.querySelector('body').style.width = '100%';
opts.display_element = document.querySelector('body');
} else {
// make sure that the display element exists on the page
var display;
if (opts.display_element instanceof Element) {
var display = opts.display_element;
} else {
var display = document.querySelector('#' + opts.display_element);
}
if(display === null) {
console.error('The display_element specified in jsPsych.init() does not exist in the DOM.');
} else {
opts.display_element = display;
}
}
opts.display_element.innerHTML = '';
DOM_container = opts.display_element;
DOM_target = document.querySelector('#jspsych-content');
// add tabIndex attribute to scope event listeners
opts.display_element.tabIndex = 0;
// add CSS class to DOM_target
if(opts.display_element.className.indexOf('jspsych-display-element') == -1){
opts.display_element.className += ' jspsych-display-element';
}
DOM_target.className += 'jspsych-content';
// set experiment_width if not null
if(opts.experiment_width !== null){
DOM_target.style.width = opts.experiment_width + "px";
}
// create experiment timeline
timeline = new TimelineNode({
timeline: opts.timeline
});
// initialize audio context based on options and browser capabilities
jsPsych.pluginAPI.initAudio();
// below code resets event listeners that may have lingered from
// a previous incomplete experiment loaded in same DOM.
jsPsych.pluginAPI.reset(opts.display_element);
// create keyboard event listeners
jsPsych.pluginAPI.createKeyboardEventListeners(opts.display_element);
// create listeners for user browser interaction
jsPsych.data.createInteractionListeners();
// add event for closing window
window.addEventListener('beforeunload', opts.on_close);
// check exclusions before continuing
checkExclusions(opts.exclusions,
function(){
// success! user can continue...
// start experiment
loadExtensions();
},
function(){
// fail. incompatible user.
}
);
function loadExtensions() {
// run the .initialize method of any extensions that are in use
// these should return a Promise to indicate when loading is complete
if (opts.extensions.length == 0) {
startExperiment();
} else {
var loaded_extensions = 0;
for (var i = 0; i < opts.extensions.length; i++) {
var ext_params = opts.extensions[i].params;
if (!ext_params) {
ext_params = {}
}
jsPsych.extensions[opts.extensions[i].type].initialize(ext_params)
.then(() => {
loaded_extensions++;
if (loaded_extensions == opts.extensions.length) {
startExperiment();
}
})
.catch((error_message) => {
console.error(error_message);
})
}
}
}
};
// execute init() when the document is ready
if (document.readyState === "complete") {
init();
} else {
window.addEventListener("load", init);
}
}
core.progress = function() {
var percent_complete = typeof timeline == 'undefined' ? 0 : timeline.percentComplete();
var obj = {
"total_trials": typeof timeline == 'undefined' ? undefined : timeline.length(),
"current_trial_global": global_trial_index,
"percent_complete": percent_complete
};
return obj;
};
core.startTime = function() {
return exp_start_time;
};
core.totalTime = function() {
if(typeof exp_start_time == 'undefined'){ return 0; }
return (new Date()).getTime() - exp_start_time.getTime();
};
core.getDisplayElement = function() {
return DOM_target;
};
core.getDisplayContainerElement = function(){
return DOM_container;
}
core.finishTrial = function(data) {
if(current_trial_finished){ return; }
current_trial_finished = true;
// remove any CSS classes that were added to the DOM via css_classes parameter
if(typeof current_trial.css_classes !== 'undefined' && Array.isArray(current_trial.css_classes)){
DOM_target.classList.remove(...current_trial.css_classes);
}
// write the data from the trial
data = typeof data == 'undefined' ? {} : data;
jsPsych.data.write(data);
// get back the data with all of the defaults in
var trial_data = jsPsych.data.get().filter({trial_index: global_trial_index});
// for trial-level callbacks, we just want to pass in a reference to the values
// of the DataCollection, for easy access and editing.
var trial_data_values = trial_data.values()[0];
// handle extension callbacks
if(Array.isArray(current_trial.extensions)){
for(var i=0; i 0) {
setTimeout(nextTrial, opts.default_iti);
} else {
nextTrial();
}
} else {
if (current_trial.post_trial_gap > 0) {
setTimeout(nextTrial, current_trial.post_trial_gap);
} else {
nextTrial();
}
}
}
core.endExperiment = function(end_message) {
timeline.end_message = end_message;
timeline.end();
jsPsych.pluginAPI.cancelAllKeyboardResponses();
jsPsych.pluginAPI.clearAllTimeouts();
core.finishTrial();
}
core.endCurrentTimeline = function() {
timeline.endActiveNode();
}
core.currentTrial = function() {
return current_trial;
};
core.initSettings = function() {
return opts;
};
core.currentTimelineNodeID = function() {
return timeline.activeID();
};
core.timelineVariable = function(varname, immediate){
if(typeof immediate == 'undefined'){ immediate = false; }
if(jsPsych.internal.call_immediate || immediate === true){
return timeline.timelineVariable(varname);
} else {
return function() { return timeline.timelineVariable(varname); }
}
}
core.allTimelineVariables = function(){
return timeline.allTimelineVariables();
}
core.addNodeToEndOfTimeline = function(new_timeline, preload_callback){
timeline.insert(new_timeline);
}
core.pauseExperiment = function(){
paused = true;
}
core.resumeExperiment = function(){
paused = false;
if(waiting){
waiting = false;
nextTrial();
}
}
core.loadFail = function(message){
message = message || 'The experiment failed to load.
';
loadfail = true;
DOM_target.innerHTML = message;
}
core.getSafeModeStatus = function() {
return file_protocol;
}
function TimelineNode(parameters, parent, relativeID) {
// a unique ID for this node, relative to the parent
var relative_id;
// store the parent for this node
var parent_node;
// parameters for the trial if the node contains a trial
var trial_parameters;
// parameters for nodes that contain timelines
var timeline_parameters;
// stores trial information on a node that contains a timeline
// used for adding new trials
var node_trial_data;
// track progress through the node
var progress = {
current_location: -1, // where on the timeline (which timelinenode)
current_variable_set: 0, // which set of variables to use from timeline_variables
current_repetition: 0, // how many times through the variable set on this run of the node
current_iteration: 0, // how many times this node has been revisited
done: false
}
// reference to self
var self = this;
// recursively get the next trial to run.
// if this node is a leaf (trial), then return the trial.
// otherwise, recursively find the next trial in the child timeline.
this.trial = function() {
if (typeof timeline_parameters == 'undefined') {
// returns a clone of the trial_parameters to
// protect functions.
return jsPsych.utils.deepCopy(trial_parameters);
} else {
if (progress.current_location >= timeline_parameters.timeline.length) {
return null;
} else {
return timeline_parameters.timeline[progress.current_location].trial();
}
}
}
this.markCurrentTrialComplete = function() {
if(typeof timeline_parameters == 'undefined'){
progress.done = true;
} else {
timeline_parameters.timeline[progress.current_location].markCurrentTrialComplete();
}
}
this.nextRepetiton = function() {
this.setTimelineVariablesOrder();
progress.current_location = -1;
progress.current_variable_set = 0;
progress.current_repetition++;
for (var i = 0; i < timeline_parameters.timeline.length; i++) {
timeline_parameters.timeline[i].reset();
}
}
// set the order for going through the timeline variables array
this.setTimelineVariablesOrder = function() {
// check to make sure this node has variables
if(typeof timeline_parameters === 'undefined' || typeof timeline_parameters.timeline_variables === 'undefined'){
return;
}
var order = [];
for(var i=0; i 1, and only when on the first variable set
if (typeof timeline_parameters.conditional_function !== 'undefined' && progress.current_repetition == 0 && progress.current_variable_set == 0) {
jsPsych.internal.call_immediate = true;
var conditional_result = timeline_parameters.conditional_function();
jsPsych.internal.call_immediate = false;
// if the conditional_function() returns false, then the timeline
// doesn't run and is marked as complete.
if (conditional_result == false) {
progress.done = true;
return true;
}
}
// if we reach this point then the node has its own timeline and will start
// so we need to check if there is an on_timeline_start function if we are on the first variable set
if (typeof timeline_parameters.on_timeline_start !== 'undefined' && progress.current_variable_set == 0) {
timeline_parameters.on_timeline_start();
}
}
// if we reach this point, then either the node doesn't have a timeline of the
// conditional function returned true and it can start
progress.current_location = 0;
// call advance again on this node now that it is pointing to a new location
return this.advance();
}
// if this node has a timeline, propogate down to the current trial.
if (typeof timeline_parameters !== 'undefined') {
var have_node_to_run = false;
// keep incrementing the location in the timeline until one of the nodes reached is incomplete
while (progress.current_location < timeline_parameters.timeline.length && have_node_to_run == false) {
// check to see if the node currently pointed at is done
var target_complete = timeline_parameters.timeline[progress.current_location].advance();
if (!target_complete) {
have_node_to_run = true;
return false;
} else {
progress.current_location++;
}
}
// if we've reached the end of the timeline (which, if the code is here, we have)
// there are a few steps to see what to do next...
// first, check the timeline_variables to see if we need to loop through again
// with a new set of variables
if (progress.current_variable_set < progress.order.length - 1) {
// reset the progress of the node to be with the new set
this.nextSet();
// then try to advance this node again.
return this.advance();
}
// if we're all done with the timeline_variables, then check to see if there are more repetitions
else if (progress.current_repetition < timeline_parameters.repetitions - 1) {
this.nextRepetiton();
// check to see if there is an on_timeline_finish function
if (typeof timeline_parameters.on_timeline_finish !== 'undefined') {
timeline_parameters.on_timeline_finish();
}
return this.advance();
}
// if we're all done with the repetitions...
else {
// check to see if there is an on_timeline_finish function
if (typeof timeline_parameters.on_timeline_finish !== 'undefined') {
timeline_parameters.on_timeline_finish();
}
// if we're all done with the repetitions, check if there is a loop function.
if (typeof timeline_parameters.loop_function !== 'undefined') {
jsPsych.internal.call_immediate = true;
if (timeline_parameters.loop_function(this.generatedData())) {
this.reset();
jsPsych.internal.call_immediate = false;
return parent_node.advance();
} else {
progress.done = true;
jsPsych.internal.call_immediate = false;
return true;
}
}
}
// no more loops on this timeline, we're done!
progress.done = true;
return true;
}
}
// check the status of the done flag
this.isComplete = function() {
return progress.done;
}
// getter method for timeline variables
this.getTimelineVariableValue = function(variable_name){
if(typeof timeline_parameters == 'undefined'){
return undefined;
}
var v = timeline_parameters.timeline_variables[progress.order[progress.current_variable_set]][variable_name];
return v;
}
// recursive upward search for timeline variables
this.findTimelineVariable = function(variable_name){
var v = this.getTimelineVariableValue(variable_name);
if(typeof v == 'undefined'){
if(typeof parent_node !== 'undefined'){
return parent_node.findTimelineVariable(variable_name);
} else {
return undefined;
}
} else {
return v;
}
}
// recursive downward search for active trial to extract timeline variable
this.timelineVariable = function(variable_name){
if(typeof timeline_parameters == 'undefined'){
return this.findTimelineVariable(variable_name);
} else {
// if progress.current_location is -1, then the timeline variable is being evaluated
// in a function that runs prior to the trial starting, so we should treat that trial
// as being the active trial for purposes of finding the value of the timeline variable
var loc = Math.max(0, progress.current_location);
// if loc is greater than the number of elements on this timeline, then the timeline
// variable is being evaluated in a function that runs after the trial on the timeline
// are complete but before advancing to the next (like a loop_function).
// treat the last active trial as the active trial for this purpose.
if(loc == timeline_parameters.timeline.length){
loc = loc - 1;
}
// now find the variable
return timeline_parameters.timeline[loc].timelineVariable(variable_name);
}
}
// recursively get all the timeline variables for this trial
this.allTimelineVariables = function(){
var all_tvs = this.allTimelineVariablesNames();
var all_tvs_vals = {};
for(var i=0; i'+
'The minimum width is '+mw+'px. Your current width is '+w+'px.
'+
'The minimum height is '+mh+'px. Your current height is '+h+'px.
';
core.getDisplayElement().innerHTML = msg;
} else {
clearInterval(interval);
core.getDisplayElement().innerHTML = '';
checkExclusions(exclusions, success, fail);
}
}, 100);
return; // prevents checking other exclusions while this is being fixed
}
}
// WEB AUDIO API
if(typeof exclusions.audio !== 'undefined' && exclusions.audio) {
if(window.hasOwnProperty('AudioContext') || window.hasOwnProperty('webkitAudioContext')){
// clear
} else {
clear = false;
var msg = 'Your browser does not support the WebAudio API, which means that you will not '+
'be able to complete the experiment.
Browsers that support the WebAudio API include '+
'Chrome, Firefox, Safari, and Edge.
';
core.getDisplayElement().innerHTML = msg;
fail();
return;
}
}
// GO?
if(clear){ success(); }
}
function drawProgressBar(msg) {
document.querySelector('.jspsych-display-element').insertAdjacentHTML('afterbegin',
'');
}
function updateProgressBar() {
var progress = jsPsych.progress().percent_complete;
core.setProgressBar(progress / 100);
}
var progress_bar_amount = 0;
core.setProgressBar = function(proportion_complete){
proportion_complete = Math.max(Math.min(1,proportion_complete),0);
document.querySelector('#jspsych-progressbar-inner').style.width = (proportion_complete*100) + "%";
progress_bar_amount = proportion_complete;
}
core.getProgressBarCompleted = function(){
return progress_bar_amount;
}
//Leave a trace in the DOM that jspsych was loaded
document.documentElement.setAttribute('jspsych', 'present');
return core;
})();
jsPsych.internal = (function() {
var module = {};
// this flag is used to determine whether we are in a scope where
// jsPsych.timelineVariable() should be executed immediately or
// whether it should return a function to access the variable later.
module.call_immediate = false;
return module;
})();
jsPsych.plugins = (function() {
var module = {};
// enumerate possible parameter types for plugins
module.parameterType = {
BOOL: 0,
STRING: 1,
INT: 2,
FLOAT: 3,
FUNCTION: 4,
KEY: 5,
SELECT: 6,
HTML_STRING: 7,
IMAGE: 8,
AUDIO: 9,
VIDEO: 10,
OBJECT: 11,
COMPLEX: 12,
TIMELINE: 13
}
module.universalPluginParameters = {
data: {
type: module.parameterType.OBJECT,
pretty_name: 'Data',
default: {},
description: 'Data to add to this trial (key-value pairs)'
},
on_start: {
type: module.parameterType.FUNCTION,
pretty_name: 'On start',
default: function() { return; },
description: 'Function to execute when trial begins'
},
on_finish: {
type: module.parameterType.FUNCTION,
pretty_name: 'On finish',
default: function() { return; },
description: 'Function to execute when trial is finished'
},
on_load: {
type: module.parameterType.FUNCTION,
pretty_name: 'On load',
default: function() { return; },
description: 'Function to execute after the trial has loaded'
},
post_trial_gap: {
type: module.parameterType.INT,
pretty_name: 'Post trial gap',
default: null,
description: 'Length of gap between the end of this trial and the start of the next trial'
},
css_classes: {
type: module.parameterType.STRING,
pretty_name: 'Custom CSS classes',
default: null,
description: 'A list of CSS classes to add to the jsPsych display element for the duration of this trial'
}
}
return module;
})();
jsPsych.extensions = (function(){
return {};
})();
jsPsych.data = (function() {
var module = {};
// data storage object
var allData = DataCollection();
// browser interaction event data
var interactionData = DataCollection();
// data properties for all trials
var dataProperties = {};
// cache the query_string
var query_string;
// DataCollection
function DataCollection(data){
var data_collection = {};
var trials = typeof data === 'undefined' ? [] : data;
data_collection.push = function(new_data){
trials.push(new_data);
return data_collection;
}
data_collection.join = function(other_data_collection){
trials = trials.concat(other_data_collection.values());
return data_collection;
}
data_collection.top = function(){
if(trials.length <= 1){
return data_collection;
} else {
return DataCollection([trials[trials.length-1]]);
}
}
/**
* Queries the first n elements in a collection of trials.
*
* @param {number} n A positive integer of elements to return. A value of
* n that is less than 1 will throw an error.
*
* @return {Array} First n objects of a collection of trials. If fewer than
* n trials are available, the trials.length elements will
* be returned.
*
*/
data_collection.first = function(n){
if (typeof n == 'undefined') { n = 1 }
if (n < 1) {
throw `You must query with a positive nonzero integer. Please use a
different value for n.`;
}
if (trials.length == 0) return DataCollection([]);
if (n > trials.length) n = trials.length;
return DataCollection(trials.slice(0, n));
}
/**
* Queries the last n elements in a collection of trials.
*
* @param {number} n A positive integer of elements to return. A value of
* n that is less than 1 will throw an error.
*
* @return {Array} Last n objects of a collection of trials. If fewer than
* n trials are available, the trials.length elements will
* be returned.
*
*/
data_collection.last = function(n) {
if (typeof n == 'undefined') { n = 1 }
if (n < 1) {
throw `You must query with a positive nonzero integer. Please use a
different value for n.`;
}
if (trials.length == 0) return DataCollection([]);
if (n > trials.length) n = trials.length;
return DataCollection(trials.slice(trials.length - n, trials.length));
}
data_collection.values = function(){
return trials;
}
data_collection.count = function(){
return trials.length;
}
data_collection.readOnly = function(){
return DataCollection(jsPsych.utils.deepCopy(trials));
}
data_collection.addToAll = function(properties){
for (var i = 0; i < trials.length; i++) {
for (var key in properties) {
trials[i][key] = properties[key];
}
}
return data_collection;
}
data_collection.addToLast = function(properties){
if(trials.length != 0){
for (var key in properties) {
trials[trials.length-1][key] = properties[key];
}
}
return data_collection;
}
data_collection.filter = function(filters){
// [{p1: v1, p2:v2}, {p1:v2}]
// {p1: v1}
if(!Array.isArray(filters)){
var f = jsPsych.utils.deepCopy([filters]);
} else {
var f = jsPsych.utils.deepCopy(filters);
}
var filtered_data = [];
for(var x=0; x < trials.length; x++){
var keep = false;
for(var i=0; iclick to download');
document.getElementById('jspsych-download-as-text-link').click();
}
//
// A few helper functions to handle data format conversion
//
// 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 (!columns.includes(key)) {
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]];
if(typeof value == 'object') {
value = JSON.stringify(value);
}
var valueString = value + "";
line += '"' + valueString.replace(/"/g, '""') + '",';
}
line = line.slice(0, -1);
result += line + '\r\n';
}
return result;
}
// this function is modified from StackOverflow:
// http://stackoverflow.com/posts/3855394
function getQueryString() {
var a = window.location.search.substr(1).split('&');
if (a == "") return {};
var b = {};
for (var i = 0; i < a.length; ++i)
{
var p=a[i].split('=', 2);
if (p.length == 1)
b[p[0]] = "";
else
b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return b;
}
return module;
})();
jsPsych.turk = (function() {
var module = {};
// core.turkInfo gets information relevant to mechanical turk experiments. returns an object
// containing the workerID, assignmentID, and hitID, and whether or not the HIT is in
// preview mode, meaning that they haven't accepted the HIT yet.
module.turkInfo = function() {
var turk = {};
var param = function(url, name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^]*)";
var regex = new RegExp(regexS);
var results = regex.exec(url);
return (results == null) ? "" : results[1];
};
var src = param(window.location.href, "assignmentId") ? window.location.href : document.referrer;
var keys = ["assignmentId", "hitId", "workerId", "turkSubmitTo"];
keys.map(
function(key) {
turk[key] = unescape(param(src, key));
});
turk.previewMode = (turk.assignmentId == "ASSIGNMENT_ID_NOT_AVAILABLE");
turk.outsideTurk = (!turk.previewMode && turk.hitId === "" && turk.assignmentId == "" && turk.workerId == "")
turk_info = turk;
return turk;
};
// core.submitToTurk will submit a MechanicalTurk ExternalHIT type
module.submitToTurk = function(data) {
var turkInfo = jsPsych.turk.turkInfo();
var assignmentId = turkInfo.assignmentId;
var turkSubmitTo = turkInfo.turkSubmitTo;
if (!assignmentId || !turkSubmitTo) return;
var dataString = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
dataString.push(key + "=" + escape(data[key]));
}
}
dataString.push("assignmentId=" + assignmentId);
var url = turkSubmitTo + "/mturk/externalSubmit?" + dataString.join("&");
window.location.href = url;
};
return module;
})();
jsPsych.randomization = (function() {
var module = {};
module.repeat = function(array, repetitions, unpack) {
var arr_isArray = Array.isArray(array);
var rep_isArray = Array.isArray(repetitions);
// if array is not an array, then we just repeat the item
if (!arr_isArray) {
if (!rep_isArray) {
array = [array];
repetitions = [repetitions];
} else {
repetitions = [repetitions[0]];
console.log('Unclear parameters given to randomization.repeat. Multiple set sizes specified, but only one item exists to sample. Proceeding using the first set size.');
}
} else {
if (!rep_isArray) {
var reps = [];
for (var i = 0; i < array.length; i++) {
reps.push(repetitions);
}
repetitions = reps;
} else {
if (array.length != repetitions.length) {
console.warning('Unclear parameters given to randomization.repeat. Items and repetitions are unequal lengths. Behavior may not be as expected.');
// throw warning if repetitions is too short, use first rep ONLY.
if (repetitions.length < array.length) {
var reps = [];
for (var i = 0; i < array.length; i++) {
reps.push(repetitions);
}
repetitions = reps;
} else {
// throw warning if too long, and then use the first N
repetitions = repetitions.slice(0, array.length);
}
}
}
}
// should be clear at this point to assume that array and repetitions are arrays with == length
var allsamples = [];
for (var i = 0; i < array.length; i++) {
for (var j = 0; j < repetitions[i]; j++) {
if(array[i] == null || typeof array[i] != 'object'){
allsamples.push(array[i]);
} else {
allsamples.push(Object.assign({}, array[i]));
}
}
}
var out = shuffle(allsamples);
if (unpack) {
out = unpackArray(out);
}
return out;
}
module.shuffle = function(arr) {
if(!Array.isArray(arr)){
console.error('Argument to jsPsych.randomization.shuffle() must be an array.')
}
return shuffle(arr);
}
module.shuffleNoRepeats = function(arr, equalityTest) {
if(!Array.isArray(arr)){
console.error('First argument to jsPsych.randomization.shuffleNoRepeats() must be an array.')
}
if(typeof equalityTest !== 'undefined' && typeof equalityTest !== 'function'){
console.error('Second argument to jsPsych.randomization.shuffleNoRepeats() must be a function.')
}
// define a default equalityTest
if (typeof equalityTest == 'undefined') {
equalityTest = function(a, b) {
if (a === b) {
return true;
} else {
return false;
}
}
}
var random_shuffle = shuffle(arr);
for (var i = 0; i < random_shuffle.length - 1; i++) {
if (equalityTest(random_shuffle[i], random_shuffle[i + 1])) {
// neighbors are equal, pick a new random neighbor to swap (not the first or last element, to avoid edge cases)
var random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
// test to make sure the new neighbor isn't equal to the old one
while (
equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
(equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) || equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1]))
) {
random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
}
var new_neighbor = random_shuffle[random_pick];
random_shuffle[random_pick] = random_shuffle[i + 1];
random_shuffle[i + 1] = new_neighbor;
}
}
return random_shuffle;
}
module.shuffleAlternateGroups = function(arr_groups, random_group_order){
if(typeof random_group_order == 'undefined'){
random_group_order = false;
}
var n_groups = arr_groups.length;
if(n_groups == 1){
console.warn('jsPsych.randomization.shuffleAlternateGroups was called with only one group. Defaulting to simple shuffle.');
return(module.shuffle(arr_groups[0]));
}
var group_order = [];
for(var i=0; i arr.length) {
console.error("Cannot take a sample " +
"larger than the size of the set of items to sample.");
}
return jsPsych.randomization.shuffle(arr).slice(0,size);
}
module.sampleWithReplacement = function(arr, size, weights) {
if(!Array.isArray(arr)){
console.error("First argument to jsPsych.randomization.sampleWithReplacement() must be an array")
}
var normalized_weights = [];
if(typeof weights !== 'undefined'){
if(weights.length !== arr.length){
console.error('The length of the weights array must equal the length of the array '+
'to be sampled from.');
}
var weight_sum = 0;
for(var i=0; i cumulative_weights[index]) { index++; }
samp.push(arr[index]);
}
return samp;
}
module.factorial = function(factors, repetitions, unpack) {
var factorNames = Object.keys(factors);
var factor_combinations = [];
for (var i = 0; i < factors[factorNames[0]].length; i++) {
factor_combinations.push({});
factor_combinations[i][factorNames[0]] = factors[factorNames[0]][i];
}
for (var i = 1; i < factorNames.length; i++) {
var toAdd = factors[factorNames[i]];
var n = factor_combinations.length;
for (var j = 0; j < n; j++) {
var base = factor_combinations[j];
for (var k = 0; k < toAdd.length; k++) {
var newpiece = {};
newpiece[factorNames[i]] = toAdd[k];
factor_combinations.push(Object.assign({}, base, newpiece));
}
}
factor_combinations.splice(0, n);
}
repetitions = (typeof repetitions === 'undefined') ? 1 : repetitions;
var with_repetitions = module.repeat(factor_combinations, repetitions, unpack);
return with_repetitions;
}
module.randomID = function(length){
var result = '';
var length = (typeof length == 'undefined') ? 32 : length;
var chars = '0123456789abcdefghjklmnopqrstuvwxyz';
for(var i = 0; i= 0) {
k = n;
} else {
k = len + n;
if (k < 0) {k = 0;}
}
var currentElement;
while (k < len) {
currentElement = O[k];
if (searchElement === currentElement ||
(searchElement !== searchElement && currentElement !== currentElement)) { // NaN !== NaN
return true;
}
k++;
}
return false;
};
}
// polyfill for Array.isArray
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}