0.2 Try the demo

Go to the demo questionnaire and fill out the brief survey.

0.3 Your first input app

Create a new RStudio project for your data input apps.

Copy the following code to a new file and save it as app.R in a new directory in this project named inputdemo. Also create a directory named responses inside the inputdemo directory. Run the app with runApp('inputdemo').

library(shiny)
library(ggplot2)

outputDir <- "responses"

# Define the fields we want to save from the form
fields <- c("name", "used_shiny", "r_num_years")

saveData <- function(input) {
  # put variables in a data frame
  data <- data.frame(matrix(nrow=1,ncol=0))
  for (x in fields) {
    var <- input[[x]]
    if (length(var) > 1 ) {
      # handles lists from checkboxGroup and multiple Select
      data[[x]] <- list(var)
    } else {
      # all other data types
      data[[x]] <- var
    }
  }
  data$submit_time <- date()
    
  # Create a unique file name
  fileName <- sprintf(
    "%s_%s.rds", 
    as.integer(Sys.time()), 
    digest::digest(data)
  )
  
  # Write the file to the local system
  saveRDS(
    object = data,
    file = file.path(outputDir, fileName)
  )
}

loadData <- function() {
  # read all the files into a list
  files <- list.files(outputDir, full.names = TRUE)
  
  if (length(files) == 0) {
    # create empty data frame with correct columns
    field_list <- c(fields, "submit_time")
    data <- data.frame(matrix(ncol = length(field_list), nrow = 0))
    names(data) <- field_list
  } else {
    data <- lapply(files, function(x) readRDS(x)) 
    
    # Concatenate all data together into one data.frame
    data <- do.call(rbind, data)
  }
  
  data
}

deleteData <- function() {
  # Read all the files into a list
  files <- list.files(outputDir, full.names = TRUE)
  
  lapply(files, file.remove)
}

resetForm <- function(session) {
  # reset values
  updateTextInput(session, "name", value = "")
  updateCheckboxInput(session, "used_shiny", value = FALSE)
  updateSliderInput(session, "r_num_years", value = 0)
}

ui <- fluidPage(
  
  # App title ----
  titlePanel("Data Collection & Feedback"),
  
  # Sidebar layout with input and output definitions ----
  sidebarLayout(
    
    # Sidebar panel for inputs ----
    sidebarPanel(
      textInput("name", "Name", ""),
      checkboxInput("used_shiny", "I've built a Shiny app before", FALSE),
      sliderInput("r_num_years", "Number of years using R",
                  0, 10, 0, ticks = FALSE),
      actionButton("submit", "Submit"),
      actionButton("clear", "Clear Form"),
      downloadButton("downloadData", "Download"),
      actionButton("delete", "Delete All Data")
    ),
    
    # Main panel for displaying outputs ----
    mainPanel(
      plotOutput(outputId = "yearsPlot"),
      tags$hr(),
      dataTableOutput("responses")
    )
  )
)

server = function(input, output, session) {
  
  # When the Submit button is clicked, save the form data
  observeEvent(input$submit, {
    saveData(input)
    resetForm(session)
  })
  
  observeEvent(input$clear, {
    resetForm(session)
  })
  
  # When the Delete button is clicked, delete all of the saved data files
  observeEvent(input$delete, {
    deleteData()
  })
  
  # Show the previous responses in a reactive table ----
  output$responses <- renderDataTable({
    # update with current response when Submit or Delete are clicked
    input$submit 
    input$delete

    loadData()
  })
  
  # Downloadable csv of selected dataset ----
  output$downloadData <- downloadHandler(
    filename = "data.csv",
    content = function(file) {
      write.csv(loadData(), file, row.names = FALSE, quote= TRUE)
    }
  )
  
  output$yearsPlot <- renderPlot({
    input$submit
    input$delete
    
    data <- loadData()
    
    ggplot(data) +
      geom_histogram(
        aes(r_num_years), 
        binwidth = 1, 
        color = "black", 
        fill = "white"
      ) +
      scale_x_continuous(
        name = "Number of years using R", 
        breaks = 0:10,
        limits = c(-0.5, 10.5)
      ) + 
      theme_minimal() +
      theme(
        text = element_text(family = "Helvetica", size = 20),
        plot.background = element_rect(fill = "white"),
        panel.grid = element_blank(),
        axis.title.y = element_blank()
      )
  })
}

shinyApp(ui, server)

0.4 Step-by-step

0.4.1 Framework

This framework gives you a full-page questionnaire with no feedback panel. You can use this framework or the one above.

library(shiny)
library(ggplot2)

outputDir <- "responses"

# Define the fields we want to save from the form
fields <- c("text_demo", 
            "select_demo", 
            "password_demo", 
            "textarea_demo", 
            "slider_demo", 
            "checkbox_demo", 
            "cbgroup_demo", 
            "date_demo", 
            "daterange_demo", 
            "number_demo")

saveData <- function(input) {
  # put variables in a data frame
  data <- data.frame(matrix(nrow=1,ncol=0))
  for (x in fields) {
    var <- input[[x]]
    if (length(var) > 1 ) {
      # handles lists from checkboxGroup and multiple Select
      data[[x]] <- list(var)
    } else {
      # all other data types
      data[[x]] <- var
    }
  }
  data$submit_time <- date()
  
  # Create a unique file name
  fileName <- sprintf(
    "%s_%s.rds", 
    as.integer(Sys.time()), 
    digest::digest(data)
  )
  
  # Write the file to the local system
  saveRDS(
    object = data,
    file = file.path(outputDir, fileName)
  )
}

loadData <- function() {
  # read all the files into a list
  files <- list.files(outputDir, full.names = TRUE)
  
  if (length(files) == 0) {
    # create empty data frame with correct columns
    field_list <- c(fields, "submit_time")
    data <- data.frame(matrix(ncol = length(field_list), nrow = 0))
    names(data) <- field_list
  } else {
    data <- lapply(files, function(x) readRDS(x)) 
    
    # Concatenate all data together into one data.frame
    data <- do.call(rbind, data)
  }
  
  data
}

# Define questions
select_demo <- selectInput(
  "select_demo", 
  "Complete these famous lyrics:  
  \"I ***** ***** ***** down in Africa\"", 
  c("", 
    "bless the waves", 
    "sense the rain", 
    "bless the rain", 
    "guess it rains"
  )
)

radio_demo <- radioButtons(
  "radio_demo", 
  "Do you like Toto?",
  c("yes", "no"), 
  inline = TRUE
)

checkbox_demo <- checkboxInput("checkbox_demo", "I consent to more 80s music references")

cbgroup_demo <- checkboxGroupInput(
  "cbgroup_demo", 
  "Which artists had a UK number one single in the 80s?",
  c("Pat Benatar" = "pb",
    "Toto" = "toto",
    "Blondie" = "blon",           # atomic 1980-03-01
    "Kraftwerk" = "kw",           # computer love 1982-02-06
    "Dog Faced Hermans" = "dfh",
    "Eurythmics" = "eur",         # there must be an angel 1985-07-27
    "T'Pau" = "tpau"              # china in your hand 1987-11-14
  )
)

number_demo <- numericInput(
  "number_demo", 
  "How many UK number one songs did Madonna have in the 80s?", 
  min = 0, max = 20, step = 1, value = 0 # answer = 6
)

slider_demo <- sliderInput(
  "slider_demo", 
  "How would you rate the 80s musically, on a scale from 0-100?",
  min = 0, max = 100, step = 1, value = 50
)

date_demo <- dateInput(
  "date_demo", 
  "Africa by Toto reached its peak position of #3 in the UK charts on what date?",
  min = "1980-01-01", max = "1989-12-31", startview="decade"
  # right answer is 1983-02-26
)

daterange_demo <- dateRangeInput(
  "daterange_demo", 
  "What was the full UK Top 100 chart run of Africa by Toto?",
  min = "1980-01-01", max = "1989-12-31", startview="decade"
  # right answer is 1983-01-29 to 1983-04-09
)

text_demo <- textInput("text_demo", "What is your favourite 80s band?")
textarea_demo <- textAreaInput("textarea_demo", "What do you think about this exercise?")
password_demo <- passwordInput("password_demo", "Tell me a secret.")
action_demo <- actionButton("clear", "Clear Form")
download_demo <- downloadButton("download", "Download")
file_demo <- fileInput("file_demo", "Upload a PDF", accept = "pdf")
help_demo <- helpText("You can write help text in your form this way")


resetForm <- function(session) {
  updateTextInput(session, "text_demo", value = "")
  updateSelectInput(session, "select_demo", selected=character(0))
  updateRadioButtons(session, "radio_demo", selected = "yes")
  updateCheckboxInput(session, "checkbox_demo", value = FALSE)
  updateCheckboxGroupInput(session, "cbgroup_demo", selected=character(0))
  updateTextAreaInput(session, "textarea_demo", value = "")
  updateTextInput(session, "password_demo", value = "")
  updateSliderInput(session, "slider_demo", value = 50)
  updateDateInput(session, "date_demo", value = NA)
  updateDateRangeInput(session, "daterange_demo", start = NA, end = NA)
  updateNumericInput(session, "number_demo", value = 0)
}

# Set up questionnaire interface ----
ui <- fluidPage(
  title = "Questionnaire Framework",
  # CSS ----
  # stop the default input containers being 300px, which is ugly
  tags$head(
    tags$style(HTML("
                    .shiny-input-container:not(.shiny-input-container-inline) {
                      width: 100%;
                      max-width: 100%;
                    }
                    "))
    ),
  
  # App title ----
  h3("My Survey"),
  
  p("Please fill out the following brief survey..."),
  
  fluidRow(
    column(width=6, text_demo),
    column(width=6, password_demo)
  ),
  
  fluidRow(
    column(width=4,
           select_demo,
           radio_demo,
           checkbox_demo
    ),
    column(width=4, 
           cbgroup_demo
    ),
    column(width=4, 
           number_demo
    )
  ),
  
  slider_demo,
  date_demo,
  daterange_demo,
  textarea_demo, 
  
  actionButton("submit", "Submit"),
  action_demo
)

# Reactive functions ----
server = function(input, output, session) {
  
  # When the Submit button is clicked, save the form data
  observeEvent(input$submit, {
    saveData(input)
    resetForm(session)
    
    # thank the user
    n_responses <- length(list.files(outputDir))
    response <- paste0("Thank you for completing the survey! You are respondant ",
                      n_responses, ".")
    showNotification(response, duration = 0, type = "message")
  })
  
  # clear the fields
  observeEvent(input$clear, {
    resetForm(session)
  })
}

shinyApp(ui, server)

0.4.2 Data Widgets

In the section for data input (sidebarPanel or fluidRow), you can add a widget for each question, then some action buttons for submitting the data or other actions. The Shiny Widgets Gallery is a useful reference for choosing the right input widgets for your questions.

selectInput
select_demo <- selectInput(
  "toto_lyrics", 
  "Complete these famous lyrics:
  \"I ***** ***** ***** down in Africa\"", 
  c("", 
    "bless the waves", 
    "sense the rain", 
    "bless the rain", 
    "guess it rains"
  )
)
radioButtons
radio_demo <- radioButtons(
  "toto_pref", 
  "Do you like Toto?",
  c("yes", "no"), 
  inline = TRUE
)
checkboxInput
checkbox_demo <- checkboxInput(
  "consent", 
  "I consent to more 80s music references"
)
checkboxGroupInput
cbgroup_demo <- checkboxGroupInput(
  "fam_bands", 
  "Which artists had a UK number one single in the 80s?",
  c("Pat Benatar" = "pb",
    "Toto" = "toto",
    "Blondie" = "blon",           # atomic 1980-03-01
    "Kraftwerk" = "kw",           # computer love 1982-02-06
    "Dog Faced Hermans" = "dfh",
    "Eurythmics" = "eur",         # there must be an angel 1985-07-27
    "T'Pau" = "tpau"              # china in your hand 1987-11-14
  )
)
numericInput
number_demo <- numericInput(
  "madonna_songs", 
  "How many UK number one songs did Madonna have in the 80s?", 
  min = 0, max = 20, step = 1, value = 0 # answer = 6
)
sliderInput
slider_demo <- sliderInput(
  "rate80", 
  "How would you rate the 80s musically, on a scale from 0-100?",
  min = 0, max = 100, step = 1, value = 50
)

(this will only display correctly in a Shiny app, not on a static webpage)

dateInput
date_demo <- dateInput(
  "africa_date", 
  "Africa by Toto reached its peak position of #3 in the UK charts on what date?",
  min = "1980-01-01", max = "1989-12-31", startview="year"
  # right answer is 1983-02-26
)

(this will only display correctly in a Shiny app, not on a static webpage)

dateRangeInput
daterange_demo <- dateRangeInput(
  "africa_date", 
  "What was the full UK Top 100 chart run of Africa by Toto?",
  min = "1980-01-01", max = "1989-12-31", startview="year"
  # right answer is 1983-01-29 to 1983-04-09
)
to

(this will only display correctly in a Shiny app, not on a static webpage)

textInput
text_demo <- textInput(
  "fav_band", 
  "What is your favourite 80s band?"
)
textAtreaInput
textarea_demo <- textAreaInput(
  "think", 
  "What do you think about this exercise?"
)
passwordInput
password_demo <- passwordInput(
  "password", 
  "Tell me a secret."
)
submitButton
submit_demo <- submitButton("Submit")
actionButton
action_demo <- actionButton(
  "clear", "Clear Form"
)

downloadButton
download_demo <- downloadButton(
  "download", "Download"
)
Download
fileInput
file_demo <- fileInput(
  "upload", "Upload a PDF", accept = "pdf"
)
helpText
help_demo <- helpText("You can write help text in your form this way")
You can write help text in your form this way

0.4.3 Data Saving

saveData <- function(input) {
  # put variables in a data frame
  data <- data.frame(matrix(nrow=1,ncol=0))
  for (x in fields) {
    var <- input[[x]]
    if (length(var) > 1 ) {
      # handles lists from checkboxGroup and multiple Select
      data[[x]] <- list(var)
    } else {
      # all other data types
      data[[x]] <- var
    }
  }
  data$submit_time <- date()
  
  # Create a unique file name
  fileName <- sprintf(
    "%s_%s.rds", 
    as.integer(Sys.time()), 
    digest::digest(data)
  )
  
  # Write the file to the local system
  saveRDS(
    object = data,
    file = file.path(outputDir, fileName)
  )
}

0.4.4 Submit Action

Use the observeEvent function to define what happens when you click the submit button. It goes inside the server function. The following code handles multiple selections and checkbox groups (which are very tricky to store in a table) and adds a submit_time column.

  # When the Submit button is clicked, save the form data
  observeEvent(input$submit, {
    saveData(input)
    resetForm(session)
  })

You’re done with a basic questionnaire at this point if you don’t want to give feedback to the participant. If you aren’t showing feedback, you can reassure the user that their response was submitted with code like showNotification(response, duration = 0, type = "message").

0.4.5 Feedback

First, you need to create a function for loading and conatenating all of the previously saved data files.

loadData <- function() {
  # read all the files into a list
  files <- list.files(outputDir, full.names = TRUE)
  
  if (length(files) == 0) {
    # create empty data frame with correct columns
    field_list <- c(fields, "submit_time")
    data <- data.frame(matrix(ncol = length(field_list), nrow = 0))
    names(data) <- field_list
  } else {
    data <- lapply(files, function(x) readRDS(x)) 
    
    # Concatenate all data together into one data.frame
    data <- do.call(rbind, data)
  }
  
  data
}

Then, you can write a function that displays feedback calculated from the loaded data. This feedback shows an interactive table of all the collected data, whenever the submit or detele buttons are pressed.

    # Show the previous responses in a reactive table ----
    output$responses <- renderDataTable({
      # update with current response when Submit or Delete are clicked
      input$submit 
      input$delete
      
      loadData()
    })

0.4.6 Upload to your R server

If you don’t have access to an R server, you can sign up for a free account at shinyapps.io. Get your token and secret and add them to the code below.

rsconnect::setAccountInfo(
  name='my-username', 
  token='my-token', 
  secret='my-secret'
)

rsconnect::deployApp("inputdemo")

0.5 Activity

  1. Create your own survey
  2. Create a feedback method
  3. Upload to a Shiny server