Participant interface

Welcome/Instruction screen

There are three good reasons to add a preface page to your experiment:

  1. the page can describe and give instructions about the experiment to come to your participants
  2. the page can serve as a consent form
  3. the page can ask the participant to type an ID

When you use PennController, every page is a trial. To add a page before our picture-selection screen, we simply insert a new PennController(...) in our script above the one we currently have.

To start with, our welcome trial will simply print some text on the page, and invite participants to click a button to start the experiment. Insert the following after PennController.ResetPrefix(null) and before the line PennController( that you already have in your script:

PennController(
    defaultText
        .print()
    ,
    newText("<p>Welcome!</p>")
    ,
    newText("<p>In this experiment, you will have to report which of two pictures matches a description.</p>")
    ,
    newText("<p>Press the <strong>F</strong> key for the picture on the left, or the <strong>J</strong> key for the picture on the right.</p>")
    ,
    newText("<p>Click the button below to start the experiment.</p>")
    ,
    newButton("Start")
        .print()
        .wait()
)

Some explanations: the first command block defaultText.print() tells the script to implicitly insert the command .print immediately below each newText command in the trial. This way, we don’t have to repeat the .print command for each of the four text elements that we create next in the welcome trial.

As you can see, we wrapped <p></p> around our texts: these are HTML tags indicating paragraphs, which browsers render with top and bottom spacing, making for a nicer visual layout. The <strong> tags print the text in boldface.

The button element is new, but it should be straightforward: as for a text element, you create a button in the cache of the trial using newButton and print it using .print. As for a key element, you pause the execution of the trial (which, in our case, would otherwise immediately reach the end) using the command .wait. Execution resumes when the button is clicked and, since there are no commands left for the trial, the experiment moves on to the next PennController trial, namely the picture-selection screen.

Collecting IDs

When recruiting participants from certain platforms like Amazon Mechanical Turk, you might want them to type in their ID so you can report it on every line in your results file.

Adding a field to the welcome page for your participants to type their ID is easy, you simply need to create and print a text input element using newTextInput and the command .print. But you also want to retrieve its content and append it to the end of each line in your results file. Here is how to do it:

PennController(
    defaultText
        .print()
    ,
    newText("<p>Welcome!</p>")
    ,
    newText("<p>In this experiment, you will have to report which of two pictures matches a description.</p>")
    ,
    newText("<p>Press the <strong>F</strong> key for the picture on the left, or the <strong>J</strong> key for the picture on the right.</p>")
    ,
    newText("<p>Please enter your ID and then click the button below to start the experiment.</p>")
    ,
    newTextInput("ID")
        .print()
    ,
    newButton("Start")
        .print()
        .wait()
    ,
    newVar("ID")
        .settings.global()
        .set( getTextInput("ID") )
)
.log( "ID" , getVar("ID") )

Note first that we didn’t use .wait on the text input: we could have done so, and it would have paused the execution of the trial before adding the button to the page, until the participant presses Enter/Return on their keyboard while typing in the input box.

More crucially, we added some code at the end of the trial. After the Start button is clicked, the script executes line 20: newVar("ID"). This creates a var element in the cache of the trial. The next line, .settings.global(), indicates that the var element we just created in the cache of the trial should also be placed into the cache of every trial coming next.
When line 22, .set( getTextInput("ID") ), gets executed, the var element stores the content of the text input element at that time. Since this line is executed after the button is clicked (remember, .wait paused the execution until the button was pressed) the input box should now contain the ID of the participant, which therefore ends up being stored in the var element.
With no lines left to be executed, it is the end of the trial, and the experiment goes on to the picture-selection trial.

The last line we added, .log( "ID" , getVar("ID") ), appears immediately after the closing parenthesis corresponding to PennController( from line 1. Its effect is to add a value to the lines in the results files generated by this welcome trial. The value that will be added to the results file is the one stored in the var element named ID.
If you test your experiment through the end and refresh your results file, you should see two lines corresponding to your welcome screen: one line for Start and one line for End (automatically reported by PennController). They should contain one more value than the lines we looked at before: that value is what you typed in the input box.

Cross-trial implementation

Since we made our var element ID global, every trial after the welcome screen can access it. This means that we can also append the command .log( "ID" , getVar("ID") ) to the closing parenthesis of our picture-selection trial, and the ID value will appear in the results lines of that trial as well. Here is our complete script so far:

PennController.ResetPrefix(null);

PennController(
    defaultText
        .print()
    ,
    newText("<p>Welcome!</p>")
    ,
    newText("<p>In this experiment, you will have to report which of two pictures matches a description.</p>")
    ,
    newText("<p>Press the <strong>F</strong> key for the picture on the left, or the <strong>J</strong> key for the picture on the right.</p>")
    ,
    newText("<p>Please enter your ID and then click the button below to start the experiment.</p>")
    ,
    newTextInput("ID")
        .print()
    ,
    newButton("Start")
        .print()
        .wait()
    ,
    newVar("ID")
        .settings.global()
        .set( getTextInput("ID") )
)
.log( "ID" , getVar("ID") )


PennController(
    newText("The fish swim in a tank which is perfectly round")
        .print()
    ,
    newImage("two", "2fishRoundTank.png")
        .settings.size(200,200)
    ,
    newImage("one", "1fishSquareTank.png")
        .settings.size(200,200)
    ,
    newCanvas(450,200)
        .settings.add(   0 , 0 , getImage("two") )
        .settings.add( 250 , 0 , getImage("one") )
        .print()
    ,
    newKey("")
        .settings.log()
        .wait()
)
.log( "ID" , getVar("ID") )

Getting the ID from the URL

Some recruiting platforms provide the option of automatically passing the participant’s ID through the URL, for example: https://expt.pcibex.net/ibexexps/example/example/experiment.html?ID=BuffyTheCat

If you’re using this method, you only need to bring minimal changes to the script above: keep the .log commands the same but replacing getVar("ID") with PennController.GetURLParameter("ID").
Just make sure that your string (here, "ID") matches the URL exactly. So if your platform uses userid=BuffyTheCat, use "userid" in GetURLParameter.

You may also want to remove the text input and the var elements altogether in your welcome-screen trial, but in the rest of this tutorial, we will assume that you are using the input box method.

Final screen

Currently, when your experiment is over, a submission confirmation message appears onto the screen, but you might want to modify this screen.

Again, the idea is to create a new PennController trial, but you want to make sure it appears after the results are saved.
You can take manual control over when, during your experiment, the results are sent using the command PennController.SendResults.

Say you want to show a link to the recruitment platform on your final screen, here is how you would do it:

PennController.ResetPrefix(null);

PennController(
    defaultText
        .print()
    ,
    newText("<p>Welcome!</p>")
    ,
    newText("<p>In this experiment, you will have to report which of two pictures matches a description.</p>")
    ,
    newText("<p>Press the <strong>F</strong> key for the picture on the left, or the <strong>J</strong> key for the picture on the right.</p>")
    ,
    newText("<p>Please enter your ID and then click the button below to start the experiment.</p>")
    ,
    newTextInput("ID")
        .print()
    ,
    newButton("Start")
        .print()
        .wait()
    ,
    newVar("ID")
        .settings.global()
        .set( getTextInput("ID") )
)
.log( "ID" , getVar("ID") )


PennController(
    newText("The fish swim in a tank which is perfectly round")
        .print()
    ,
    newImage("two", "2fishRoundTank.png")
        .settings.size(200,200)
    ,
    newImage("one", "1fishSquareTank.png")
        .settings.size(200,200)
    ,
    newCanvas(450,200)
        .settings.add(   0 , 0 , getImage("two") )
        .settings.add( 250 , 0 , getImage("one") )
        .print()
    ,
    newKey("")
        .settings.log()
        .wait()
)
.log( "ID" , getVar("ID") )


PennController.SendResults()


PennController(
    newText("<p>Thank you for your participation!</p>")
        .print()
    ,
    newText("<p><a href='https://platform/link.html'>Click here to validate your participation.</a></p>")
        .print()
    ,
    newButton("void")
        .wait()
)

When it reaches the end of the picture-selection trial, after the F or the J key has been pressed, the script now exectues the line PennController.SendResults() and therefore proceeds to sending the participant’s responses to your results file. Once the results have been saved, the script goes on to the next and final trial, which contains a text element with the HTML <a> tag to generate a link.

The last commands to be executed in this final PennController trial are newButton("void"), which creates a button element in the cache of the trial, and .wait() which pauses the execution until the button is clicked. But since .print was never encountered, the button never gets added to the page, and therefore execution never resumes, leaving the text on the page forever.


Next: Audio, Text unfolding and Clicks