/** * 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 += '
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