About

This tutorial shows you how to implement an id check in a shiny app using the ShinyPsych package.

Sometimes when running a study, it is helpfull to be able to check whether a participant has already participated in this (or another) study earlier. We've used this feature for a study we ran in several batches on Amazon MTurk. To make sure that a participant couldn't participate in two batches, we checked the id at the beginning of the experiment and, if she was not allowed to participate, displayed a page with this information from which she couldn't proceed in the experiment.

In this tutorial, we will show you how to implement such an id check. Piece by piece we will go through the whole app code, with comments and explanations between the pieces. To see the app working, click here. In this app we will use a local database containing the ids already in use. This database is stored in the app directory. You could also load a database from dropbox, but the larger your database gets, the more you may want to have it in a local directory to minimize loading time.

If you have any questions about the package, just email us at markus.d.steiner@gmail.com or Nathaniel.D.Phillips.is@gmail.com. Ok let's get started with the app...

The Check Id App

We subdivided the script in eight sections:

Section 0: Load Libraries

library(shiny)
library(shinyjs)
library(ShinyPsych)

This Survey app only relies on these three libraries (and their dependencies). The shiny library is, clue is in the name, the basis to create working shiny apps. It can be used to create html pages and dynamic interfaces, e.g. to display dynamic plots etc. shinyjs is a usefull tool to bring some javascript logic in the page, e.g. to control whether a button is disabled, i.e. nothing happens if you click it, and then to enable it, once e.g. a necessary input has been given. And ShinyPsych about which you'll learn more now...

Section A: Assign External Values

# Vector with page ids used to later access objects
idsVec <- c("Instructions", "Goodbye")

# create page lists for the instructions and the last page
instructions.list <- createPageList(fileName = "Instructions_CheckId",
                                    globId = "Instructions")
goodbye.list <- createPageList(fileName = "Goodbye")

First we define idsVec, which includes the names of four lists that create pages (such as displaying text or having survey questions on it), that we will use. You do not need to use these exact names, but whatever you call it here, has to match what you call it at different other places later.

The createPageList() function that is called afterwards loads in .txt files, that are called fileName.txt, i.e. if fileName = "Goodbye", R will then search for a file named Goodbye.txt in the current directory, if it is no default list (in this case it is a default list). So if you have not stored your file in the app directory but, e.g. in the www folder of your app, make sure to also enter the path, e.g. fileName = "www/Goodbye". There are some default files, such as the four loaded in here. If you do not use a default list, make sure to set the defaulttxt argument of createPageList() to FALSE. Note that for the check id instructions list we had to add the argument globId = "Instructions" because the default is just the fileName, but this way it is easier to change tasks and so on.

The page lists read in contain, among other things, the names of shiny functions and arguments to those (for an in depth tutorial on this click here). These functions are then called to create the html logic needed to set up these pages and this code is returned, together with other things, as a list and saved, e.g., as instructions.list. These html code snippets are then later used to display the respective pages.

Now all the lists are prepared and we go on to start defining the actual app.

Section B: Define Overall Layout

ui <- fixedPage(

  # App title
  title = "ShinyCheckId",
  uiOutput("MainAction"),

  # For Shinyjs functions
  useShinyjs(),

  # include appropriate css and js scripts
  includeScriptFiles()

)

server <- function(input, output, session) {

  output$MainAction <- renderUI( {
    PageLayouts()

  })


# The server function continues, which is why the curly brackets are not closed

The first part assigned to ui, is the usual shiny ui part (if that's news for you, we recommend on reading up on shiny apps first). The title will be displayed in the tab bar. useShinyjs() is needed to allow shinyjs functions to be used. includeScriptFiles() will include some css and javascript scripts we've written for the tasks. Since we have no behavioral tasks in this app we don't need to specify additional arguments. If not otherwise specified with globalScript = FALSE, a css script will be loaded that specifies some parameters such as the font size. If you have additional own css or js files to include, you can do this with shiny's includeCSS() and includeScript() functions. Note that all of our javascript scripts have two versions: a commented version that you can look at in the package directory to see what it's doing, and a compiled version (indicated through the Comp.js at the end of the filename), that was compiled by using Google's closure compiler to make it less readable. This is done to make it a bit harder to check or change the variables in the console.
Please note the session in the server definition. You must have for the javascript communicatoin to work.

Section C: Define Reactive Values

  # CurrentValues controls page setting such as which page to display
  CurrentValues <- createCtrlList(firstPage = "instructions", # id of the first page
                                  globIds = idsVec,           # ids of pages for createPage
                                  complCode = TRUE,           # create a completion code
                                  complName = "EP-CheckId")   # first element of completion code

This function sets up the list of reactive values (again, if that's not a familiar term you should consider to read about shiny apps first) needed to control settings and page navigation. createCtrlList() is used to set up the general control list that navigates you through the experiment by containing the current page value and things like that. The firstPage argument indicates the id of the first page. This will be the first thing you see when you run the app. The globIds are the list ids defined earlier in section A as idsVec. complCode and complName control whether a completion code of the form "complName-XXX-XXX-XXX", where XXX is a random number between 100 and 999, should be generated.

Section D: Page Layouts

  PageLayouts <- reactive({

    # insert created completion code that it can later be displayed
    goodbye.list <- changePageVariable(pageList = goodbye.list, variable = "text",
                                       oldLabel = "completion.code",
                                       newLabel = CurrentValues$completion.code)

    # display instructions page
    if (CurrentValues$page == "instructions") {

      return(
        # create html logic of instructions page
        createPage(pageList = instructions.list,
                   pageNumber = CurrentValues$Instructions.num,
                   globId = "Instructions", ctrlVals = CurrentValues)
      )}

    if (CurrentValues$page == "not allowed"){
      return(
        createNotAllowedPage(input, "Instructions")
        )
    }


    # P5) Goodbye
    if (CurrentValues$page == "goodbye") {

      return(
        createPage(pageList = goodbye.list, pageNumber = CurrentValues$Goodbye.num,
                   globId = "Goodbye", ctrlVals = CurrentValues, continueButton = FALSE)
      )}

  })

PageLayouts is a reactive expression in which the page layouts are defined. First the goodbye.list is updated with the completion code. It has a placeholder in the list for the completion code that is inserted with changePageVariable(). The pages are then created using the createPage() function by giving it the in section A created page lists, the reactive control values created in section C and the global id, i.e. the respective id from idsVec. The pageNumber argument controls which page of the current list is to be displayed. You can see that all pages based on a previously created page list are set up in the same form with createPage().

createNotAllowedPage() can be used to create the page to be displayed in case the participant id matches with an id already in the database. The page displays an information that the participant is not allowed to participate and shows him the id he entered. You could of course also create an own page. Just print the createNotAllowedPage code to see how you could create a page and change it according to your liking.

Section F: Event (e.g. Button) Actions

This section is again subdivided in two subsections, one of which is controlling the navigation through the app (F1) and the other is controlling some events, such as enabling the continue buttons (F2).

Section F1: Page Navigation Button

  observeEvent(input[["Instructions_next"]],{
    nextPage(pageId = "instructions", ctrlVals = CurrentValues, nextPageId = "goodbye",
             pageList = instructions.list, globId = "Instructions",
             checkAllowed = TRUE, checkAllowedPage = 1,
             checkIdVar = "workerid", checkLocation = "local",
             checkSep = ",", checkHeader = TRUE, checkFileName = "Id_Database.txt",
             checkNotAllowedId = "not allowed", inputList = input)
  })

The block observes a button named the same as the strings in the double brackets (here Instructions_next) and will, once it receives input from the observed button, call the function indicated in the curly brackets. The nextPage()function handles the flow through pages created with createPage() from an existing page list. Each time the button is clicked, it will increase the page number of that page id by 1, until the maximum number of pages in that list is reached (for Instructions this maximum is 2) and will then go to the page indicated at nextPageId. Compared to the other app tutorials that use this function, here we specify some additional arguments that all except inputList, which is the shiny input object, start with check. These arguments must be specified in order for the id to be checked (for this the checkId() function will be called). checkAllowed indicates whether an id check should be executed (default is FALSE). checkAllowedPage indicates the page number of the current list (here instructions.list) after which you want to execute the check. Since the id is entered on page one we set this to 1, such that when you continue from page number 1 to 2 the check is executed. checkIdVar is the id of the text input element you want to check (without the globId name). checkLocation is the location at which the database (a csv file with one column named ids containing the ids) is stored. This can be either local or dropbox. checkSep and checkHeader are passed to read.table() or drop_read_csv(), depending on the location. checkFileName is the database file name. checkNotAllowedId is the page id of the page to be displayed if the given id matches one from the database.

#### Section F2: Event Control

  # Make sure answers are selected
  observeEvent(reactiveValuesToList(input),{

    onInputEnable(pageId = "instructions", ctrlVals = CurrentValues,
                  pageList = instructions.list, globId = "Instructions",
                  inputList = input, charNum = 4)

  })

}  

onInputEnable() checks for prespecified conditions to be met, an if TRUE, enables the continue button. This function is designed for use with a page list. The conditions are specified in the page list. Usually these conditions are that an input mustn't be NULL because in many input fields, if nothing has been given as input yet, it just yields NULL. However you may also include a minimum character check such as in onInputEnable() called for the Instructions page list. On the first page of the Instructions list, you have to enter an id. The check will only be ok if, in this case at least 4 (because of charNum = 4) characters are given as input. Only then will the button be enabled. Note that the observed input object is in this case reactiveValuesToList(input), which basically means that every input object is observed. That's why onInputEnable() first does a check if you're currently on the correct page (e.g. instructions in the first call), before it does anything else. This might not be very efficient but it saves you from having to enter every input variable to check to observeEvent() and is therefore particularly usefull if you have a larger number of checks, e.g. in a questionnaire with many items on the same page.

Section G: Create App

# Create app!
shinyApp(ui = ui, server = server)

The last step is to create the app.



ndphillips/ShinyPsych documentation built on Feb. 14, 2022, 5:53 p.m.