/**
* jspsych-free-sort
* plugin for drag-and-drop sorting of a collection of images
* Josh de Leeuw
*
* documentation: docs.jspsych.org
*/
jsPsych.plugins['free-sort'] = (function() {
var plugin = {};
jsPsych.pluginAPI.registerPreload('free-sort', 'stimuli', 'image');
plugin.info = {
name: 'free-sort',
description: '',
parameters: {
stimuli: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Stimuli',
default: undefined,
array: true,
description: 'items to be displayed.'
},
stim_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus height',
default: 100,
description: 'Height of items in pixels.'
},
stim_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Stimulus width',
default: 100,
description: 'Width of items in pixels'
},
scale_factor: {
type: jsPsych.plugins.parameterType.FLOAT,
pretty_name: 'Stimulus scaling factor',
default: 1.5,
description: 'How much larger to make the stimulus while moving (1 = no scaling)'
},
sort_area_height: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sort area height',
default: 800,
description: 'The height of the container that subjects can move the stimuli in.'
},
sort_area_width: {
type: jsPsych.plugins.parameterType.INT,
pretty_name: 'Sort area width',
default: 800,
description: 'The width of the container that subjects can move the stimuli in.'
},
sort_area_shape: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Sort area shape',
options: ['square','ellipse'],
default: 'ellipse',
description: 'The shape of the sorting area'
},
prompt: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Prompt',
default: '',
description: 'It can be used to provide a reminder about the action the subject is supposed to take.'
},
prompt_location: {
type: jsPsych.plugins.parameterType.SELECT,
pretty_name: 'Prompt location',
options: ['above','below'],
default: 'above',
description: 'Indicates whether to show prompt "above" or "below" the sorting area.'
},
button_label: {
type: jsPsych.plugins.parameterType.STRING,
pretty_name: 'Button label',
default: 'continue',
description: 'The text that appears on the button to continue to the next trial.'
}
}
}
plugin.trial = function(display_element, trial) {
var start_time = performance.now();
let html =
'
';
// another div for border
html += ''
} else {
html += 'webkit-border-radius: 0%; moz-border-radius: 0%; border-radius: 0%">
'
}
if ( trial.prompt ) {
trial.prompt = ' '
}
else {
trial.prompt = ''
}
// variable that has the prompt text, counter and button
const html_text = '
' + trial.prompt +
'
You still need to place ' + trial.stimuli.length + ' items inside the arena.
'+
'
'
// position prompt above or below
if (trial.prompt_location == "below") {
html += html_text
} else {
html = html_text + html
}
console.log(html)
display_element.innerHTML = html;
// store initial location data
let init_locations = [];
// determine number of rows and colums, must be a even number
let num_rows = Math.ceil(Math.sqrt(trial.stimuli.length))
if ( num_rows % 2 != 0) {
num_rows = num_rows + 1
}
// compute coords for left and right side of arena
let r_coords = [];
let l_coords = [];
for (const x of make_arr(0, trial.sort_area_width - trial.stim_width, num_rows) ) {
for (const y of make_arr(0, trial.sort_area_height - trial.stim_height, num_rows) ) {
if ( x > ( (trial.sort_area_width - trial.stim_width) * .5 ) ) {
//r_coords.push({ x:x, y:y } )
r_coords.push({ x:x + (trial.sort_area_width) * .5 , y:y });
} else {
l_coords.push({ x:x - (trial.sort_area_width) * .5 , y:y });
//l_coords.push({ x:x, y:y } )
}
}
}
// repeat coordinates until you have enough coords (may be obsolete)
while ( ( r_coords.length + l_coords.length ) < trial.stimuli.length ) {
r_coords = r_coords.concat(r_coords)
l_coords = l_coords.concat(l_coords)
}
// reverse left coords, so that coords closest to arena is used first
l_coords = l_coords.reverse()
// shuffle stimuli, so that starting positions are random
trial.stimuli = shuffle(trial.stimuli);
let inside = []
for (let i = 0; i < trial.stimuli.length; i++) {
let coords = []
if ( (i % 2) == 0 ) {
coords = r_coords[Math.floor(i * .5)];
} else {
coords = l_coords[Math.floor(i * .5)];
}
display_element.querySelector("#jspsych-free-sort-arena").innerHTML += ''+
'';
init_locations.push({
"src": trial.stimuli[i],
"x": coords.x,
"y": coords.y
});
inside.push(false);
}
// moves within a trial
let moves = [];
// are objects currently inside
let cur_in = false
// draggable items
const draggables = display_element.querySelectorAll('.jspsych-free-sort-draggable');
// button (will show when all items are inside) and border (will change color)
const border = display_element.querySelector("#jspsych-free-sort-border")
const button = display_element.querySelector('#jspsych-free-sort-done-btn')
for(let i=0; i 1 ) {
display_element.querySelector("#jspsych-free-sort-counter").innerHTML = "You still need to place " + (inside.length - inside.filter(Boolean).length) + " items inside the arena."
} else {
display_element.querySelector("#jspsych-free-sort-counter").innerHTML = "You still need to place " + (inside.length - inside.filter(Boolean).length) + " item inside the arena."
}
}
}
document.addEventListener('mousemove', mousemoveevent);
var mouseupevent = function(e){
document.removeEventListener('mousemove', mousemoveevent);
elem.style.transform = "scale(1, 1)";
if (inside.every(Boolean)) {
border.style.background = "#a1d99b";
border.style.borderColor = "#a1d99b";
} else {
border.style.background = "none";
border.style.borderColor = "#fc9272";
}
moves.push({
"src": elem.dataset.src,
"x": elem.offsetLeft,
"y": elem.offsetTop
});
document.removeEventListener('mouseup', mouseupevent);
}
document.addEventListener('mouseup', mouseupevent);
});
}
display_element.querySelector('#jspsych-free-sort-done-btn').addEventListener('click', function(){
if (inside.every(Boolean)) {
const end_time = performance.now();
const rt = end_time - start_time;
// gather data
const items = display_element.querySelectorAll('.jspsych-free-sort-draggable');
// get final position of all items
let final_locations = [];
for(let i=0; i