Merge pull request #948 from bjoluc/fix-duplicate-body

Delay `init()` execution until the document is ready
This commit is contained in:
Josh de Leeuw 2020-10-28 11:49:28 -04:00 committed by GitHub
commit d621710625
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 201 additions and 145 deletions

View File

@ -52,158 +52,166 @@ window.jsPsych = (function() {
// //
core.init = function(options) { core.init = function(options) {
function init() {
if(typeof options.timeline === 'undefined'){
console.error('No timeline declared in jsPsych.init. Cannot start experiment.')
}
if(typeof options.timeline === 'undefined'){ if(options.timeline.length == 0){
console.error('No timeline declared in jsPsych.init. Cannot start experiment.') console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.')
} }
if(options.timeline.length == 0){ // reset variables
console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.') timeline = null;
} global_trial_index = 0;
current_trial = {};
current_trial_finished = false;
paused = false;
waiting = false;
loaded = false;
loadfail = false;
jsPsych.data.reset();
// reset variables var defaults = {
timeline = null; 'display_element': undefined,
global_trial_index = 0; 'on_finish': function(data) {
current_trial = {}; return undefined;
current_trial_finished = false; },
paused = false; 'on_trial_start': function(trial) {
waiting = false; return undefined;
loaded = false; },
loadfail = false; 'on_trial_finish': function() {
jsPsych.data.reset(); return undefined;
},
'on_data_update': function(data) {
return undefined;
},
'on_interaction_data_update': function(data){
return undefined;
},
'on_close': function(){
return undefined;
},
'preload_images': [],
'preload_audio': [],
'preload_video': [],
'use_webaudio': true,
'exclusions': {},
'show_progress_bar': false,
'message_progress_bar': 'Completion Progress',
'auto_update_progress_bar': true,
'auto_preload': true,
'show_preload_progress_bar': true,
'max_load_time': 60000,
'max_preload_attempts': 10,
'default_iti': 0,
'minimum_valid_rt': 0,
'experiment_width': null
};
var defaults = { // override default options if user specifies an option
'display_element': undefined, opts = Object.assign({}, defaults, options);
'on_finish': function(data) {
return undefined; // set DOM element where jsPsych will render content
}, // if undefined, then jsPsych will use the <body> tag and the entire page
'on_trial_start': function(trial) { if(typeof opts.display_element == 'undefined'){
return undefined; // check if there is a body element on the page
}, var body = document.querySelector('body');
'on_trial_finish': function() { if (body === null) {
return undefined; document.documentElement.appendChild(document.createElement('body'));
}, }
'on_data_update': function(data) { // using the full page, so we need the HTML element to
return undefined; // have 100% height, and body to be full width and height with
}, // no margin
'on_interaction_data_update': function(data){ document.querySelector('html').style.height = '100%';
return undefined; document.querySelector('body').style.margin = '0px';
}, document.querySelector('body').style.height = '100%';
'on_close': function(){ document.querySelector('body').style.width = '100%';
return undefined; opts.display_element = document.querySelector('body');
}, } else {
'preload_images': [], // make sure that the display element exists on the page
'preload_audio': [], var display;
'preload_video': [], if (opts.display_element instanceof Element) {
'use_webaudio': true, var display = opts.display_element;
'exclusions': {}, } else {
'show_progress_bar': false, var display = document.querySelector('#' + opts.display_element);
'message_progress_bar': 'Completion Progress', }
'auto_update_progress_bar': true, if(display === null) {
'auto_preload': true, console.error('The display_element specified in jsPsych.init() does not exist in the DOM.');
'show_preload_progress_bar': true, } else {
'max_load_time': 60000, opts.display_element = display;
'max_preload_attempts': 10, }
'default_iti': 0, }
'minimum_valid_rt': 0, opts.display_element.innerHTML = '<div class="jspsych-content-wrapper"><div id="jspsych-content"></div></div>';
'experiment_width': null 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, with or without preloading
if(opts.auto_preload){
jsPsych.pluginAPI.autoPreload(timeline, startExperiment, opts.preload_images, opts.preload_audio, opts.preload_video, opts.show_preload_progress_bar);
if(opts.max_load_time > 0){
setTimeout(function(){
if(!loaded && !loadfail){
core.loadFail();
}
}, opts.max_load_time);
}
} else {
startExperiment();
}
},
function(){
// fail. incompatible user.
}
);
}; };
// override default options if user specifies an option // execute init() when the document is ready
opts = Object.assign({}, defaults, options); if (document.readyState === "complete") {
init();
// set DOM element where jsPsych will render content
// if undefined, then jsPsych will use the <body> 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 { } else {
// make sure that the display element exists on the page window.addEventListener("load", init);
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 = '<div class="jspsych-content-wrapper"><div id="jspsych-content"></div></div>'; }
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, with or without preloading
if(opts.auto_preload){
jsPsych.pluginAPI.autoPreload(timeline, startExperiment, opts.preload_images, opts.preload_audio, opts.preload_video, opts.show_preload_progress_bar);
if(opts.max_load_time > 0){
setTimeout(function(){
if(!loaded && !loadfail){
core.loadFail();
}
}, opts.max_load_time);
}
} else {
startExperiment();
}
},
function(){
// fail. incompatible user.
}
);
};
core.progress = function() { core.progress = function() {

View File

@ -0,0 +1,48 @@
require("../../jspsych");
require("../../plugins/jspsych-html-keyboard-response");
describe("jsPsych init", () => {
beforeEach(() => {
document.body.innerHTML = "";
});
function setReadyState(targetState) {
jest
.spyOn(document, "readyState", "get")
.mockImplementation(() => targetState);
}
function getBodyHTML() {
return document.body.innerHTML;
}
function init() {
jsPsych.init({
timeline: [
{
type: "html-keyboard-response",
stimulus: "foo",
},
],
});
}
it("should delay execution until the document is ready", () => {
expect(getBodyHTML()).toBe("");
setReadyState("loading");
init();
expect(getBodyHTML()).toBe("");
// Simulate the document getting ready
setReadyState("complete");
window.dispatchEvent(new Event("load"));
expect(getBodyHTML()).not.toBe("");
});
it("should execute immediately when the document is ready", () => {
// The document is ready by default in jsdom
init();
expect(getBodyHTML()).not.toBe("");
});
});