jsPsych icon indicating copy to clipboard operation
jsPsych copied to clipboard

Allow editing trials' parameters during simulation mode

Open nikbpetrov opened this issue 2 years ago • 3 comments

In customized experiments with lots of behaviour modified on_start and on_load, I find myself further extending the function to include cases of what happens during simulation mode, e.g. speed up an animation in simulation mode or reduce the trial duration.

It would be nice if a modifiable copy of the trials is available in simulation_options that I can use to modify the trial's parameters before the trial runs, similar to the on_start behaviour.

nikbpetrov avatar Dec 20 '22 12:12 nikbpetrov

Hey @nikbpetrov I'm finally getting around to triaging some of these issues.

What do you imagine the syntax would look like for this?

jodeleeuw avatar May 04 '23 02:05 jodeleeuw

Same as for on_start makes sense to me.

To give one (very simplified) example from a recent experiment. Instead of this (focus on the trial_duration parameter):

let leapfrog_trial = {
	type: jsPsychHtmlKeyboardResponse,
	stimulus: `<div style="font-size:3em; color: choose;">CHOOSE</div>
				<div style="display: flex; justify-content: space-around; font-size: 2em; margin-top: 2em;">
					<div>OPTION A</div>
					<div>OPTION B</div>
				</div>`,
	choices: ['arrowleft', 'arrowright'],
	trial_duration: SIMULATE ? SIMULATE_TRIAL_DURATION*1.2 : LEAPFROG_TRIAL_TIME_LIMIT,
	simulation_options: {
		data: function () {
			if (jsPsych.randomization.sampleBernoulli(0.95)) {
				return {rt: SIMULATE_TRIAL_DURATION}
			} else {
				return {rt: null, response: null}
			}
		}
	}
}

I would prefer this:

let leapfrog_trial = {
	type: jsPsychHtmlKeyboardResponse,
	stimulus: `<div style="font-size:3em; color: choose;">CHOOSE</div>
				<div style="display: flex; justify-content: space-around; font-size: 2em; margin-top: 2em;">
					<div>OPTION A</div>
					<div>OPTION B</div>
				</div>`,
	choices: ['arrowleft', 'arrowright'],
	trial_duration: LEAPFROG_TRIAL_TIME_LIMIT,
	simulation_options: {
		data: function () {
			if (jsPsych.randomization.sampleBernoulli(0.95)) {
				return {rt: SIMULATE_TRIAL_DURATION}
			} else {
				return {rt: null, response: null}
			}
		},
		trial: function(trial) {
			trial.trial_duration = SIMULATE_TRIAL_DURATION*1.2
		}
	}
}

If necessary, I can look for more examples where this sort of thing can get incredibly useful from my previous experiments, but I think you get the point. For me, the biggest thing is that it's much cleaner as it constrains all simulation-related stuff within the simulation_options.

Ideally there would be a way to execute custom functions within simulation_options similar to on_load behaviour. Specifically, I would want to solve cases like the one below, where I have a custom trial, say an HTML form, and I get to control how behaviour is simulated without modifying my plugin file (though not sure the latter makes perfect sense, but it definitely feels easier for quick experiments):

[note how much stuff I have to modify in on_load]

let admc_sn2_trial = {
	type: jsPsychSurveyHtmlForm,
	preamble: `<p><b><u>Instructions:</u><br>The following problems ask <i>out of 100 people your age</i>, how many would say that it is sometimes OK to do different things. For Each question, please select a number between 0 (meaning <i>no one</i> thinks that it is sometimes OK) and 100 (meaning <i>everyone</i> thinks that it is sometimes OK).</b></p>
				<p style="margin-top: 3em;"><b>Out of 100 people your age, how many would say it is sometimes OK ...</b></p>`,
	html: function() {
		html = ''
		SN2_ITEMS.forEach(e => {
			html += 
			`<div class="cal-statement">${e.question}</div>
			<div class="slider-labels-container">
				<input name="${e.id}" id="${e.id}_slider" type="range" min="0" max="100" step="1" value="0" class="jspsych-slider" style="width: 100%">
				<div class="labels-container2">
					<div style="left: calc(0% - (10%/2) - -7.5px);"><span>0<br>No one</span></div>
					<div style="left: calc(10% - (10%/2) - -6px);"><span>10</span></div>
					<div style="left: calc(20% - (10%/2) - -4.5px);"><span>20</span></div>
					<div style="left: calc(30% - (10%/2) - -3px);"><span>30</span></div>
					<div style="left: calc(40% - (10%/2) - -1.5px);"><span>40</span></div>
					<div style="left: calc(50% - (10%/2) - 0px);"><span>50</span></div>
					<div style="left: calc(60% - (10%/2) - 1.5px);"><span>60</span></div>
					<div style="left: calc(70% - (10%/2) - 3px);"><span>70</span></div>
					<div style="left: calc(80% - (10%/2) - 4.5px);"><span>80</span></div>
					<div style="left: calc(90% - (10%/2) - 6px);"><span>90</span></div>
					<div style="left: calc(100% - (10%/2) - 7.5px);"><span>100<br>Everyone</span></div>
				</div>
			</div>`
		})
		html += `<p id="slider_text" style="text-align: center"><i>Move all sliders to continue.</i></p>`
		return html
	},
	on_load: function() {
		let next_button = document.querySelector("#jspsych-survey-html-form-next")
		if (!(ignore_validation === true)) {
			next_button.disabled = true

			let sliders_moved = {}
			document.querySelectorAll(`input[type="range"]`).forEach(e=>{
				sliders_moved[e.id] = false
				// these 3 event listeners might be replaced by a single 'input' one
				// but in this case we are just preserving the jsPsych style as per plugin-html-slider-response.js
				e.addEventListener("mousedown", () => handle_slider_change(e.id, sliders_moved, next_button))
				e.addEventListener("touchstart", () => handle_slider_change(e.id, sliders_moved, next_button))
				e.addEventListener("change", () => handle_slider_change(e.id, sliders_moved, next_button))
			})
		}

		if (SIMULATE) {
			next_button.disabled = false;
			document.querySelectorAll(`input[type="range"]`).forEach(e=>e.value=jsPsych.randomization.randomInt(0, 100))
		}
	}
};

nikbpetrov avatar May 04 '23 05:05 nikbpetrov

Thanks, this is great. It makes total sense to me and shouldn't be too hard to add.

jodeleeuw avatar May 04 '23 13:05 jodeleeuw