Up to this point our experiments have been mostly linear in structure. Although we have seen examples of nested timelines, randomised timelines, repeated timelines and so on, none of the example experiments we’ve built so far allow us to explicitly rely on programming constructs like “if” (to create conditional branching) or “while” (to build customised loops). The jaysire package provides two functions that provide these programming constructs, display_if() and display_while(). The display_if() function allows you to indicate that a timeline should be executed only if a particular condition holds, and the display_while() function allows you to specify that a timeline should execute over and over again until the condition no longer holds. At the moment, jaysire is quite limited in terms of what conditions are natively supported. Later releases will (I hope) extend this functionality.

Branching timelines

Suppose the first question in our experiment asks people to indicate whether they identify as LGBTIQ+, like so:

page1 <- trial_html_button_response(
  "Do you identify as LGBTIQ+?",
  c("Yes", "No", "Prefer not to say")
)

If the user response “Yes”, we might want to ask a follow-up question, one that asks them to indicate (if they are willing to do so) which subcategories they belong to. However, we wouldn’t want to show that question to anyone who responds “No” or “Prefer not to say”, since the question isn’t relevant to those people. How do we do this? First, let’s just build the trial the same way we normally would:

followup <- trial_survey_multi_select(
  question_multi(
    prompt = "Select all that apply",
    options = c(
      "Lesbian", 
      "Gay", 
      "Bisexual/Pansexual", 
      "Transgender", 
      "Nonbinary",
      "Genderqueer", 
      "Intersex", 
      "Asexual",
      "Other"
    )
  )
)

If we add followup to our timeline “as is”, it will be shown to everybody regardless of what answer they provide. To fix this, what we do is wrap followup into a timeline, and then pipe that timeline through a display_if() statement. What that looks like is this:

page1a <- build_timeline(followup) %>%
  display_if(fn_data_condition(button_pressed == "0"))

What we have done here is defined page1a as a conditional timeline. When the participant actually completes the experiment, this trial will only be shown if the condition fn_data_condition(button_pressed == "0") is true. This solves our problem, but it may not be obvious how we’re solving the problem, so it is worth unpacking this a little…

What is going on here?

Firstly, let’s take a look at the button_pressed == "0" part. Within jsPsych, a button response is recorded numerically: it creates an internal “button_pressed” variable to store the results of the trial, where a value of 0 indicates that the user pressed the first button, 1 refers to the second button, and so on. However, everything in jsPsych is internally stored as text, so if we want to check the response we have to use "0" rather than 0. This is the reason why the condition statement takes the form button_pressed == "0": the “Yes” response is the first response option for the page1 trial, so we only want page1a to execute if the response on that trial (i.e., the value of button_pressed within the jsPsych data storage) is equal to "0".

Next, I’ll expand on what is going on with the fn_data_condition() function, but to do so I need to explain the display_if() function in a little more detail. First, let’s rewrite the code above without using the pipe, and name our arguments explicitly:

page1a <- display_if(
  timeline = build_timeline(followup), 
  conditional_function = fn_data_condition(button_pressed == "0")
)

This isn’t quite so pretty to look at, but it is helpful for the purposes of understanding. The display_if() function takes two arguments, the timeline object itself (i.e., the followup page), and a conditional_function that is used to determine whether or not the timeline should be executed. Because the experiment is eventually run in javascript through the web browser, this function must be a javascript function, not an R function. If you know javascript, then it may be useful to note that jaysire contains an insert_javascript() function that means that any text you include will be passed as unfiltered javascript, so you can in fact pass anything you like here:

page1a <- display_if(
  timeline = build_timeline(followup), 
  conditional_function = insert_javascript("/* your javascript function here */")
)

If your javascript function returns true – logical values in javascript are true or false unlike in R where they are TRUE or FALSE – then the timeline will execute. If it returns false the timeline will not execute.

However, one of the goals of jaysire is to minimise the amount of javascript you have to write when building a behavioural experiment, so there is a helper function called fn_data_condition() that will create the javascript function that you want. By default, what it does is inspect the contents of the jsPsych data store for the preceding trial, and allows the user to construct an expression that will be tested against that data (e.g., button_pressed == "0"). This simplifies matters a little, but my intention is to extend this functionality over time to allow you to deal with the most common use cases without ever having to write your own javascript. Nevertheless, this is a work in progress.

Looping timelines

Having gone into all this detail for display_if(), it is very simple to provide an illustration of display_while() because it works in exactly the same fashion. Suppose I want to force people to tell me that this picture of a heart looks pleasant. Let’s construct a trial:

resources <- system.file("extdata", "resources", package = "jaysire")

query <- trial_image_button_response(
  stimulus = insert_resource("heart.png"), 
  stimulus_height = 400,
  stimulus_width = 400,
  choices = c("Unpleasant", "Neutral", "Pleasant"),
  prompt = "You will not be allowed to continue unless you select 'Pleasant'"
)

Now what we do is take this query trial, wrap into a timeline using build_timeline() and then keep repeating that trial until the user responds by pressing button "2" (i.e., selects "Pleasant"):

page2 <- build_timeline(query) %>%
  display_while(fn_data_condition(button_pressed != "2"))

At this point we are done! Let’s wrap all this up in a single timeline, add the resources, and build it as an experiment:

build_experiment(
  timeline = build_timeline(page1, page1a, page2),
  resources = build_resources(resources),
  path = temporary_folder(), 
  on_finish = save_locally()
)
#> Warning in dir.create(path): '/tmp/Rtmp4bRbsp/jaysire_6wpp6' already exists

Summary

library(jaysire)

resources <- system.file("extdata", "resources", package = "jaysire")

page1 <- trial_html_button_response(
  "Do you identify as LGBTIQ+?",
  c("Yes", "No", "Prefer not to say")
)

followup <- trial_survey_multi_select(
  question_multi(
    prompt = "Select all that apply",
    options = c("Lesbian", "Gay", "Bisexual/Pansexual", "Transgender",
      "Nonbinary", "Genderqueer", "Intersex", "Asexual", "Other")
  )
)

page1a <- build_timeline(followup) %>%
  display_if(fn_data_condition(button_pressed == "0"))

query <- trial_image_button_response(
  stimulus = insert_resource("heart.png"),
  stimulus_height = 400,
  stimulus_width = 400,
  choices = c("Unpleasant", "Neutral", "Pleasant"),
  prompt = "You will not be allowed to continue unless you select 'Pleasant'"
)

page2 <- build_timeline(query) %>%
  display_while(fn_data_condition(button_pressed != "2"))

build_experiment(
  timeline = build_timeline(page1, page1a, page2),
  resources = build_resources(resources),
  path = temporary_folder(),
  on_finish = save_locally()
)

You can check out a working version of the experiment here.