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) {
function init() {
if(typeof options.timeline === 'undefined'){
console.error('No timeline declared in jsPsych.init. Cannot start experiment.')
}
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.')
}
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;
jsPsych.data.reset();
// reset variables
timeline = null;
global_trial_index = 0;
current_trial = {};
current_trial_finished = false;
paused = false;
waiting = false;
loaded = false;
loadfail = 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;
},
'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 = {
'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;
},
'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
// 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 <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 {
// 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 = '<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.
}
);
};
// 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 <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');
// execute init() when the document is ready
if (document.readyState === "complete") {
init();
} 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;
}
window.addEventListener("load", init);
}
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() {

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("");
});
});