This tutorial shows you how to implement a survey in a shiny app using the ShinyPsych package. If you haven’t already, download the package from GitHub by running this code:
devtools::install_github("ndphillips/ShinyPsych", build_vignettes = TRUE)Surveys are one of the most important methods of gathering informations in studies. ShinyPsych provides a way of including surveys and instructions in an app by simply giving it a text file with the survey.
In this tutorial, we will show you a questionnaire implementation. 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.
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…
We subdivided the script in eight sections:
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…
# Dropbox directory to save data
outputDir <- "msteiner/ShinyPsych/Survey"
# Vector with page ids used to later access objects
idsVec <- c("Instructions", "Survey", "Demographics", "Goodbye")
# create page lists for the instructions and the last page
instructions.list <- createPageList(fileName = "Instructions_Survey",
globId = "Instructions")
survey.list <- createPageList(fileName = "Survey_Example",
globId = "Survey")
demographics.list <- createPageList(fileName = "Demographics")
goodbye.list <- createPageList(fileName = "Goodbye")We first define outputDir, which contains the path we use in the dropbox to later save the data. If you use dropbox you can also define such an opject or later directly give the string as an argument.
Next 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 survey 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 survey.list. These html code snippets are then later used to display the respective pages. The order of items of the survey used here is randomized (also here for details on how to set this up click on the link above).
Now all the lists are prepared and we go on to start defining the actual app.
ui <- fixedPage(
# App title
title = "ShinySurvey",
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 closedThe 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.
# 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-Survey") # first element of completion codeThis 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.
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)
)}
# display survey page
if (CurrentValues$page == "survey") {
return(
# create html logic of instructions page
createPage(pageList = survey.list,
pageNumber = CurrentValues$Survey.num,
globId = "Survey", ctrlVals = CurrentValues)
)}
if (CurrentValues$page == "demographics"){
return(
createPage(pageList = demographics.list, pageNumber = CurrentValues$Demographics.num,
globId = "Demographics", ctrlVals = CurrentValues)
)}
# 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().
observeEvent(input[["Demographics_next"]], {(
# Create progress message
withProgress(message = "Saving data...", value = 0, {
incProgress(.25)
# Create a list to save data
data.list <- list( "id" = input$Instructions_workerid, # participant id
"color" = input$Survey_qu1, # answer to question 1
"capital" = input$Survey_qu2, # answer to question 2
"swallow" = input$Survey_qu3, # answer to question 3
"like.survey" = input$Survey_qu4, # answer to question 4
"age" = input$Demographics_age, # stated age
"sex" = input$Demographics_sex, # stated sex
"input.order" = survey.list$id.order) # order of ids as shown (
# since order may have been
# randomized)
)
# save Data
saveData(data.list, location = "dropbox", outputDir = outputDir,
partId = data.list$id, suffix = "_g")
CurrentValues$page <- "goodbye"
})
)})
}The last observeEvent() block again tracks a continue button. Note that each page created with createPage() with the continueButton argument set to TRUE (default) has a continue button with the id globId_next, which is why here Demographics_next is observed. What this block then does is before it sets the current page variable to in this case goodbye, it prepares a data list containing all the data we want to be saved and then saving it by calling saveData(). Note that data list must either contain variables of length one, or of the same lengths, because in saveData() will call as.data.frame(data.list). So if you have differing lengths it will throw an error. saveData() in this case writes the data to dropbox, which is why we need to give it the output directory for dropbox which we specified in Section A. The saved file will be of the form paste0(partid, Sys.time(), digest::digest(data.list), suffix, ".csv") to ensure no file will overwrite another one. Note that in order to save a file to dropbox you need to give your access tokens for dropbox to the function. You have to put them in an .rds file and give the name (if you have it in the www folder with the path so www/droptoken.rds) to the function. Default is droptoken = "droptoken.rds" which is also what my access token file is called, thus I didn’t have to specify this here. You can, however, also save your files locally when you run the app on a local computer. Just use location = "local" and then specify where you want to have you file in outputDir. Note that the creation of the data list and the save data function are wrapped in withProgress() to which also incProgress() belongs. It displays a little panel indicating the progress to the user.
# Create app!
shinyApp(ui = ui, server = server)The last step is to create the app.