Customizing an experiment
In this section, we’ll add the option of selecting an image with a mouse click, use CSS styles, create a trial timeout and a trial delay, and add a completion screen.
Selecting an image with a mouse click
Currently, the participant can only select an image by pressing the F
or J
key. We’ll use a Selector element to include the option of clicking on an image to select it.
A Selector creates a group of elements, where (by default) each member can be selected by a mouse click. We can then associate a key to each member, so that a keypress also selects the desired element.
- Remove the
"keypress"
Key. - Create a Selector named
"selection"
. Add
the"singular"
and"plural"
Image elements to the Selector.- Call the
keys
command to associate theF
andJ
keys to the singular and plural images, respectively. Log
information about the Selector element.- Call the
once
command so that only the first image selection is valid (without theonce
command, the participant can change which image they select). - Call the
wait
command to pause experiment script execution until the participant selects an element.
@// code omitted in interest of space
@
@// Experimental trial
@Template("items.csv", row =>
@ newTrial("experimental-trial",
@ newAudio("audio", row.audio)
@ .play()
@ ,
@ newText("sentence", row.sentence)
@ .center()
@ .unfold(row.duration)
@ ,
@ newImage("singular", row.singular_image)
@ .size(200, 200)
@ ,
@ newImage("plural", row.plural_image)
@ .size(200, 200)
@ ,
@ newCanvas("side-by-side", 450,200)
@ .add( 0, 0, getImage("plural"))
@ .add(250, 0, getImage("singular"))
@ .center()
@ .print()
@ ,
- newKey("keypress", "FJ")
- .log()
- .wait()
+ newSelector("selection")
+ .add(getImage("plural"), getImage("singular"))
+ .keys("F", "J")
+ .log()
+ .once()
+ .wait()
@ ,
@ getAudio("audio")
@ .wait("first")
@ )
@ .log("group", row.group)
@ .log("item", row.item)
@ .log("condition", row.inflection)
@)
Using CSS styles for vertical spacing
We’ll update the instructions to reflect the clicking option. We’ll also use the cssContainer
command to replace the <p></p>
HTML tags on some Text elements with a 1em bottom margin on all Text elements.
- Create a new Text element named
"instructions-5"
. - Call the
cssContainer
command on thedefaultText
object to add a 1em bottom margin to every Text element container. - Remove the
<p></p>
tags from the"instructions-2"
and"instructions-4"
Text elements.
@// code omitted in interest of space
@
@// Instructions
@newTrial("instructions",
@ defaultText
+ .cssContainer({"margin-bottom":"1em"})
@ .center()
@ .print()
@ ,
@ newText("instructions-1", "Welcome!")
@ ,
! newText("instructions-2", "In this experiment, you will hear and read a sentence, and see two images.")
@ ,
@ newText("instructions-3", "<b>Select the image that better matches the sentence:</b>")
@ ,
! newText("instructions-4", "Press the <b>F</b> key to select the image on the left.<br>Press the <b>J</b> key to select the image on the right.<br>You can also click on an image to select it.")
+ ,
+ newText("instructions-5", "If you do not select an image by the time the audio finishes playing,<br>the experiment will skip to the next sentence.")
@ ,
@ newButton("wait", "Click to start the experiment")
@ .center()
@ .print()
@ .wait()
@)
@
@// code omitted in interest of space
Creating a timeout
Currently, the "experimental-trial"
trial ends after audio playback finishes or an image is selected, whichever comes second.
We’ll modify the trial so that it ends after whichever comes first. In other words, the participant has until the audio playback finishes to select an image before the trial times out and ends.
The timeout uses a Timer element and a callback
command. A callback
command specifies an element with some command(s), and when the callback
command is evaluated it triggers the execution of the specified command(s) on the specified element.
- Create a Timer named
"timeout"
that isrow.duration
ms long. - Start the Timer.
- Remove the
once
andwait
commands on the"selection"
Selector. - Call the
callback
command on the"selection"
Selector. When an image is selected, thecallback
command will stop the"timeout"
Timer. - Refer back to the
"timeout"
Timer and call thewait
command to pause experiment script execution until the Timer stops. - Remove the
getAudio("audio").wait("first")
block.
@// code omitted in interest of space
@
@// Experimental trial
@Template("items.csv", row =>
@ newTrial("experimental-trial",
@ newAudio("audio", row.audio)
@ .play()
+ ,
+ newTimer("timeout", row.duration)
+ .start()
@ ,
@ newText("sentence", row.sentence)
@ .center()
@ .unfold(row.duration)
@ ,
@ newImage("plural", row.plural_image)
@ .size(200, 200)
@ ,
@ newImage("singular", row.singular_image)
@ .size(200, 200)
@ ,
@ newCanvas("side-by-side", 450,200)
@ .add( 0, 0, getImage("plural"))
@ .add(250, 0, getImage("singular"))
@ .center()
@ .print()
@ ,
@ newSelector("selection")
@ .add(getImage("plural"), getImage("singular"))
@ .keys("F", "J")
@ .log()
- .once()
- .wait()
+ .callback(getTimer("timeout").stop())
+ ,
+ getTimer("timeout")
+ .wait()
@ ,
- getAudio("audio")
- .wait("first)
@ )
@ .log("group", row.group)
@ .log("item", row.item)
@ .log("condition", row.inflection)
@)
The timeout is created as follows:
- The
"timeout"
Timer starts after the"audio"
Audio starts playing. Both elements arerow.duration
ms long. - Experiment script execution continues as normal until PennController reaches the
wait
command on the"timeout"
Timer. Thewait
command pauses experiment script execution until PennController detects an end-of-timer event.- If the participant selects an image before audio playback finishes, the image selection calls the
callback
command on the"selection"
Selector, which triggers the execution ofgetTimer("timeout").stop()
. The Timer stops, and validates thewait
command. - If audio playback finishes before the participant selects an image, the
"timeout"
Timer stops naturally, and validates thewait
command.
- If the participant selects an image before audio playback finishes, the image selection calls the
Adding a trial delay
Each trial begins as soon as the previous trial ends. This might be overwhelming for participants, so we’ll create a one-second pause between trials.
- Create and start a Timer named
"break"
that is 1000ms long. - Call the
wait
command on the"break"
Timer to pause experiment script execution until the timer stops.
@// code omitted in interest of space
@
@// Experimental trial
@Template("items.csv", row =>
@ newTrial("experimental-trial",
+ newTimer("break", 1000)
+ .start()
+ .wait()
@ ,
@ newAudio("audio", row.audio)
@ .play()
@ ,
@ newTimer("timeout", row.duration)
@ .start()
@ ,
@ newText("sentence", row.sentence)
@ .center()
@ .unfold(row.duration)
@ ,
@ newImage("plural", row.plural_image)
@ .size(200, 200)
@ ,
@ newImage("singular", row.singular_image)
@ .size(200, 200)
@ ,
@ newCanvas("side-by-side", 450,200)
@ .add( 0, 0, getImage("plural"))
@ .add(250, 0, getImage("singular"))
@ .center()
@ .print()
@ .log()
@ ,
@ newSelector("selection")
@ .add(getImage("plural"), getImage("singular"))
@ .keys("F", "J")
@ .log()
@ .callback(getTimer("timeout").stop())
@ ,
@ getTimer("timeout")
@ .wait()
@ )
@ .log("group", row.group)
@ .log("item", row.item)
@ .log("condition", row.inflection)
@)
Adding a completion screen
By default, PennController sends an experiment’s results to the PCIbex Farm server after all the trials have ended. However, we can use the global command SendResults
to manually control when results are sent.
We’ll add a completion screen to the end of the experiment, and send results right before the completion screen trial actually begins. Since the completion screen is still part of the experiment, this will help ensure that participants don’t close their web browser before the results from the experimental trials are sent to the server.
As usual, we’ll use a Button and a wait
command to pause experiment script execution during the completion screen, so that participants have time to read it. This time though, we’ll force the experiment to pause indefinitely by not printing the Button; since the participant can’t click the Button, the wait
command is never satisifed.
You can use this trick whenever you want to permanently stop a participant from continuing!
- Call the
SendResults
global command and label it"send"
. - Create a new trial labeled
"completion_screen"
. - Create and print a centered Text named
"thanks"
. - Create a new Button named
"void"
. Call thewait
command on it, but do not print it to the screen.
@// code omitted in interest of space
@
@// Experimental trial
@Template("items.csv", row =>
@ // code omitted in interest of space
@)
@
+// Send results manually
+SendResults("send")
@
+// Completion screen
+newTrial("completion_screen",
+ newText("thanks", "Thank you for participating! You may now exit the window.")
+ .center()
+ .print()
+ ,
+ newButton("void", "")
+ .wait()
+)