Last week, we learned how to build simple web pages using R Markdown. Over the next few weeks, we will learn to build more complex web pages using a combination of HTML and JavaScript. In particular, we will learn to use a JavaScript library called JSPsych. (Note: A library is a little like an R package – a set of functions for doing useful tasks in a programming language). JSPsych is created by Josh de Leeuw, and this tutorial is based on his introduction.
Create a new directory for this week’s lab, and then create a subdirectory called SimpleExpt. Download the lab zip file from Learn. Place it in the SimpleExpt directory and unzip it.
What does the zip file contain?
css contains jspsych.css; a css file contains layout instructions for the static components of a webpage (e.g., how large should the default text be; how should the different elements be arranged).jspsych.js is a JavaScript file (that’s what .js stands for). It contains a set of functions for accessing all the other aspects of the JSPsych package.plugins contains a large number of .js files. Each file (called a plugin here) contains JavaScript code, and that code provides a template for specifying the dynamic appearance of a webpage, i.e., how the appearance of that webpage can change, particularly with regard to how the user can respond to and change that webpage (e.g., should the website display an image? should the user press a button in response to that image?)tests&examples contains web pages that exemplify how JSPsych can be used. Try opening the file demo-simple-rt-task.html in your web browser.In this lab, you will go through some instructions for how to create that website. In the next lab, we will significantly modify the code, in order to turn that website into a game for a tablet computer.
At its simplest, a web page is simple an HTML document. HTML stands for hyper-text mark-up language; so, a web page is a mark up document (like R Markdown!), but one that also has hyper-links embedded in the text.
A neat fact about R Studio is that it can be used to edit both HTML documents and JavaScript documents. So, we can continue to use R Studio for this entire project.
Create an HTML file in R Studio. Go to File > New > R HTML. Save the file (File > Save) in the SimpleExpt directory (call it, e.g., Simple_Expt). You’ll note that this isn’t really an HTML file, but an RHTML file. An RHTML file is an R file that can be used to generate an HTML document; you’ll learn more about this in a moment.
Basic R HTML file
Let’s run through the components of the web page.
The information between the <body> </body> tags is displayed in the web browser. In this file, that information is of a few different types.
<p> tags define a paragraph of text.<b> tags make the text boldface.<rcode> tags allow you to insert R code into your website. We won’t be using that for now, however.This tutorial is drawn from Josh de Leeuw’s JSPsych documentation. You will be creating a simple web experiment in which your participants see a line of text, and press a button to make it disappear.
Delete all the material within the <body></body> tags, so that your HTML file looks like this:
Basic R HTML file
Look at your HTML file. Do you see the <head> tags? The area between these tags is similar to the Header on an RMarkdown document, and it is in this area where we can import our different libraries and plugins.
We need to import two different JavaScript libraries.
And we also need to import the JSPsych .CSS file (remember CSS from earlier?)
tags:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="jspsych-5.0.3/jspsych.js"></script>
<link href="jspsych-5.0.3/css/jspsych.css" rel="stylesheet" type="text/css"></link>
The first line imports jquery from a remote website run by Google. The second line imports JSPsych from the folder on your computer (you need to ensure that your HTML file, and the jspsych-5.0.3 folder are in the same directory). The third line imports the CSS file (CSS files aren’t strictly speaking “scripts”, so they aren’t loaded using the <script> command).
Knit your webpage. It should compile, but you should not see any text (because the body is blank).
Remember plugins from before? Now, we will use a plugin to print some text on your webpage.
First, you need to import the text plugin (you’ll notice that there’s a lot of importing when using JSPsych!). Take a look at this code below, and try to explain to yourself what it does.
<script src="jspsych-5.0.3/plugins/jspsych-text.js"></script>
This file also needs to go in the Head of your document, along with the calls to the other .js files.
Remember the body tags? We are going to add some code, which the website will display. Importantly, that code will be JavaScript code, rather than HTML code. So, we add some script tags that go below the body tags. Then, the code in the <script> tags will generate HTML code, that will in turn be displayed to participants. After you add the <script> tags, then your HTML should now look like this:
<body>
</body>
<script>
</script>
Add the following JavaScript within the <script> tags. This code
hello_trial (that’s the bit that says var hello_trial)= {...} part). In particular, it assigns two key:Value pairs
type which has the value texttext which has the value “Hello World!”The type key defines this as an object created by the JSPsych Text plugin, which you imported before. The Text key defines what text should be displayed.
Note These Key: Value pairs are surrounded by curly braces
{}; the braces bind these values together into a single object.
var hello_trial = {
type: 'text',
text: 'Hello world!'
}
The previous section created a variable that contains information about the text plugin. Now, we have to tell the website to show that information to the user.
The code below will do this. It calls a function called jsPsych.init().
timeline.Timeline indicates the order in which your JSPsych variables will be shown on the webpage (at the moment, you only have one variable, but this will be critical later).timeline is an array.
c() command in R). In this case, the variables are collected together using square braces [].hello_trialThis code should be put below var hello_trial
jsPsych.init({
timeline: [ hello_trial ]
})
Note jsPsych.init() is one of the JSPsych functions that was imported when you called
<script src="jspsych-5.0.3/jspsych.js"></script>
Does it display correctly? Yell if it does not. When you press a button, the text should disappear
Remember that the timeline’s value is an array, which can have multiple components.
Create a second text var, whose name is different from hello_trial, and whose text is also different. e.g., you could call the var goodbye_trial, and give it some text that says goodbye.
This second var should go between the first var and the call to jspsych.init()
Now, add the second var to the timeline: timeline: [hello_trial, goodbye_trial]. Knit the webpage, and see what happens. If all has gone well, your first text should disappear when a key is pressed, and the second text should appear. Then, the second text should disappear when a key is pressed again.
Your final webpage should look a little like this:
Sadly, we all make mistakes, and so our code will often contain errors. Sometimes, those mistakes are so severe that the computer will not be able to display your webpage. If that is the case, how can you find out what the problem is?
The easiest way to find out about bugs in your code is to use the “developer mode” in your web browser. That will require you to leave R Studio, and look at your webpage in the browser (i.e., Chrome, Safari or Firefox) just as we did earlier in the lab.
Then, with the webpage open on your browser, you can go into Developer mode.
The Console section on this screen will print any error messages.
Sometimes, it might be a bit hard to figure out what the error message means, but trial and error is your friend here. Note that in the right hand corner is a little link to the portion of your script where the error is found; by clicking on that, you may be able to figure out where the mistake it.
Try and create a deliberate mistake in your code (e.g., delete the curly bracket after hello_trial), then save your code and launch it in a web browser, and then see if you can understand the error message that is printed in the console of Developer Tools.
Now, we will try to create a more complex study, based on the “simple rt experiment” that you tried earlier.
RTExpt. Download the lab .zip file again, and unzip it in this directory.RTExpt.rhtml and save it in the RTExpt directory. <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="jspsych-5.0.3/jspsych.js"></script>
<script src="jspsych-5.0.3/plugins/jspsych-text.js"></script>
<link href="jspsych-5.0.3/css/jspsych.css" rel="stylesheet" type="text/css"></link>
<script> </script> tags under the <body> tags.The first thing that participants usually see in an experiment is an introductory screen. We will use the jspsych text plugin to create an introductory welcome message to participants. This is very similar to what you were doing in the previous task.
var welcome_block = {
type: "text",
text: "Welcome to the experiment. Press any key to begin."
};
Next, we are going to display that message on the screen. To do this, we’ll use a slightly different technique from the last example, which gives you a little more flexibility and control.
In the previous example we displayed the text using the code below:
jsPsych.init({
timeline: [ hello_trial]
})
Remember that the function jsPsych.init() had an argument (a key) called timeline, and that it took your text trial hello_trial as an argument.
This way of doing things can get hard to programme when you have many, many trials. You would need to write a piece of code that looks like this:
jsPsych.init({
timeline: [ hello_trial1, hello_trial2, hello_trial3, hello_trial4, hello_trial5, hello_trial6, hello_trial7, hello_trial8, hello_trial9, hello_trial10,hello_trial11, hello_trial12, hello_trial13, hello_trial13, hello_trial14, hello_trial15,hello_trial16....hello_trial180...]
})
The code above is hard to read and understand. Don’t do it!
Instead, what we will do is create a new variable in which we pre-specify all the different trials, and then we’ll pass that variable as the argument of timeline.
var trial_structure = [];
trial_structure.push(welcome_block);
Some of the operations here should be familiar. Remember that var creates a new variable. [] says that this new variable is an array.
But the last operation, trial_structure.push(welcome_block);, is new. In this operation, we are taking the array trial_structure and we are “pushing”, i.e., adding, the variable welcome_block to that array.
If you do this correctly, then the value of the variable trial_structure should now be [welcome_block]. Shout if this does not make sense!
Now, you can use trial_structure as the timeline argument for jsPsych.init():
jsPsych.init({
timeline: trial_structure
});
Check that your experiment knits and runs.
Now, we will create a slightly more complex piece of text that displays the instructions
var instructions_block = {
type: "text",
text: "<p>In this experiment, a circle will appear in the center " +
"of the screen.</p><p>If the circle is <strong>blue</strong>, " +
"press the letter F on the keyboard as fast as you can.</p>" +
"<p>If the circle is <strong>orange</strong>, do not press " +
"any key.</p>" +
"<div class='left center-content'><img src='jspsych-5.0.3/tests&examples/img/blue.png'></img>" +
"<p class='small'><strong>Press the F key</strong></p></div>" +
"<div class='right center-content'><img src='jspsych-5.0.3/tests&examples/img/orange.png'></img>" +
"<p class='small'><strong>Do not press a key</strong></p></div>" +
"<p>Press any key to begin.</p>"
};
Place this text below the bit of code that creates welcome_block.
Here, we are creating the same type of object as before, i.e., a text object, but we are giving it some extra formatting using HTML tags. The value of the text key is still a line of text; but it is a line of text that contains HTML formatting. The HTML tags look a little like RMarkdown tags. For instance, where RMarkdown uses **two asterisks** to indicate boldface, HTML uses <strong></strong>.
We use the little
+signs to glue the lines of text together into one long line of text (else each new line would be a different piece of text, and thetextkey can only take one piece of text as its value; Shout if you don’t understand this).
You can also see that we are displaying images in this bit of HTML:
<div class='left center-content'><img src='jspsych-5.0.3/tests&examples/img/blue.png'></img>" +
"<p class='small'><strong>Press the F key</strong></p></div>" +
<div> tag indicates that we are working on one section of the screen, called class='left center-content'>.<img src='jspsych-5.0.3/tests&examples/img/blue.png'></img> says that we are including an image, and src points to where on your computer that image should be (in folder jspsych-5.0.3 > tests&examples > img).As an exercise, let’s play with the image paths. Create a new folder within the folder RTExpt called
img. Copy the imagesblue.pngandorange.pnginto that folder from their previous location (see thesrcfor a hint if you don’t know where that is). Edit the src for each of the images above to point to the new location of the figures.
Now, push your instructions_block to the trial_structure array:
trial_structure.push(instructions_block);
In each trial of this experiment, participants will be shown a circle and they will then either press a key if the circle is blue, or withold a response if the circle is orange.
Creating a trial in JSPsych is very similar to creating a text screen, except that we use a slightly more complex plugin in order to make our stimulus timing more precise.
We will use a plugin called jspsych-single-stim.js. Load it as before, up in the <head> of your website: <script src="jspsych-5.0.3/plugins/jspsych-single-stim.js"></script>
Now, add some code down in the <script> section of your file to create two single-stim trials:
var blue_trial = {
type: 'single-stim',
stimulus: 'img/blue.png',
choices: ['F'],
timing_response: 1500
};
var orange_trial = {
type: 'single-stim',
stimulus: 'img/orange.png',
choices: ['F'],
timing_response: 1500
}
Here, we’ve created a variable called blue_trial of type single_stim which shows the blue.png as its stimulus, and allows participants to respond only by pressing the f key on their keyboard. The participants have 1500ms to respond to this trial. The same is also true for orange_trial, except that the image is different.
Now, add these two trials to your timeline
trial_structure.push(blue_trial, orange_trial);
You should now have a somewhat functional experiment, which shows an image on each trial and takes an F key as a response; trials end after 1.5s if the participant hasn’t responded.
This is pretty good! But what if we wanted to have loads and loads of different trials? We’d have to create loads and loads of new vars, which is inefficient.
JSPsych allows us to overcome this inefficiency by using nesting
var test_block = {
type: 'single-stim',
choices: ['F'],
timeline: [
{stimulus: 'img/blue.png'},
{stimulus: 'img/orange.png'}
],
timing_response: 1500
}
The var test_block is exactly the same as the previous trials, except that it has a timeline key:value pair. Here, the timeline takes an array [] that includes details of two stimuli:
timeline: [
{stimulus: 'img/blue.png'},
{stimulus: 'img/orange.png'}
]
JSPsych is clever enough to realise that, when you write this, what you want is to create two trials that are identical except that they have different stimuli.
Now, delete the parts of your experiment that created and pushed blue_trial and orange_trial, and replace them with var test_block. Shout if you get confused.
We can modify test_block so that it shows trials in random order, using a simple Key:Value pair. Insert randomize_order: true, within test_block and see if it works (if it does not work, try to figure out why, but shout if you can’t do that quickly).
Now, we are going to modify test_block so that it has 10 trials rather than two. To do this, we are going re-use a trick from Section 5.1, where we created a variable for the timeline. Remember that currently test_block is structured as so:
var test_block = {
type: 'single-stim',
choices: ['F'],
randomize_order: true,
timeline: [
{stimulus: 'img/blue.png'},
{stimulus: 'img/orange.png'}
]
}
We are going to create a new variable that has twenty different trials in, and then plug that variable in to test_block as a Value for the Timeline key, so that it looks as follows:
var test_block = {
type: 'single-stim',
choices: ['F'],
randomize_order: true,
timeline: all_trials
}
First, create a new variable called test_stimuli, that contains the values from the timeline on test_block:
var test_stimuli = [
{stimulus: 'img/blue.png'},
{stimulus: 'img/orange.png'}
];
Now, we will use a function called jsPsych.randomization.repeat() to create 5 copies of test_stimuli (i.e., 5 sets of 2 trials, 10 trials total) in random order:
var all_trials = jsPsych.randomization.repeat(test_stimuli, 5);
Then, we will plug all_trials into test_block:
var test_block = {
type: 'single-stim',
choices: ['F'],
randomize_order: true,
timeline: all_trials
}
If you like, you can take out randomize_order: true, now, because the use of jsPsych.randomization.repeat() means that our stimuli are already in random order.
Hopefully you have already built the new test_block by now, but if not go ahead. Remember to think of the order in which variables should be created. First you create test_stimuli, then you randomize it, then you create your block of test trials.
JSPsych automatically records most of the important data that you collect, such as the time it takes participants to respond to a stimulus, or what key they press. You can see the data that is automatically collected by adding the following Key: Value pair to the function JSPsych.init().
on_finish: function() {
jsPsych.data.displayData();
}
This bit of code should be pasted onto a new line below the Timeline: Key:Value pair, and you should ensure that there is a comma at the end of the Timeline Key:Value pair.
When you run your RHTML file, the final screen should now contain a list of all the data collected on each screen of the experiment (with key_press indicating the numeric code for the key; -1 is no response).
From this new data file, you can see the responses for each stimulus; e.g., you can see what happens on trials where the stimulus was blue.png and orange.png.
It would be helpful to include additional information in this datafile, such as whether each trial was a Go trial or a No-Go trial. This is quite simple to do by augmenting the files that create the test trial.
For instance, you should edit the code for test_stimuli
var test_stimuli = [
{stimulus: 'img/blue.png'},
{stimulus: 'img/orange.png'}
];
And augment it to include information on the trial type
var test_stimuli = [
{
stimulus: "img/blue.png",
data: {
response: 'go'
}
},
{
stimulus: "img/orange.png",
data: {
response: 'no-go'
}
}
];
What we have done here is include an additional Key:Value pair. The Key is called data, and the value is another key set of key value pairs, which are enclosed in curly braces {}. This new set of Key:Value pairs is included in the datafile (see this by looking at the datafile that is now created).
Congrats! By now, you should have created a working Go/No-Go experiment. It isn’t quite finished yet (the data doesn’t really save for example), but we’ll work on that in the future.
If you were able to do this very quickly, then you can also augment your experiment with some additional fancy stuff, by following the rest of Josh de Leeuw’s tutorial here.