I am writing a Shiny program which manipulates a dataset the user uploads. The dataset has fixed column names and I create several UI elements (selectInputs) to filter that dataset.
Reprex looks like this:
ui <- fluidPage(
fluidRow(selectInput("filter_a","label",choices = c("a","b","c"),multiple = T),
selectInput("filter_b","label",choices = c("x","z","y"),multiple = T),
dataTableOutput("o1"),
br(),
dataTableOutput("o2")
)
)
server <- function(input, output) {
df <- reactive({
df <- data.frame(a = c("a","b","c"),
b = c("x","z","y"))
})
filter_function_1 <- reactive({
req(data)
df <- df()
if(!is.null(input$filter_a)){
df <- df %>%
filter(df$a %in% input$filter_a)
}
if(!is.null(input$filter_b)){
df <- df %>%
filter(df$b %in% input$filter_b)
}
return(df)
})
output$o1 <- renderDataTable({filter_function_1()})
While this works it looks like very bad practice. In my actual program I have a set of 14 filters and wrapping it 14 times and applying the same just doesnt look right to me.
Wanting to simplify I came up with this. I have a feeling that this is also not best practice (addressing the input$filter_a by concatenating strings doesnt seem right).
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("input$", filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
filter_function_2 <- reactive({
df <- df()
df <- df %>%
filter_func(arg="a") %>%
filter_func(arg="b")
return(df)
})
output$o2 <- renderDataTable({filter_function_2()})
}
Now, this looks cleaner to me, but I still want to modulize the code even more and have the filter function and code resign in a file. There are more data prep steps involved and I want to be able to debug them easily, hence the separate files / functions.
Code might look now like this:
filter_data.R
filter_func <- function(df, arg) {
filter_arg <- paste0("filter_", arg)
filter <- paste0("input$", filter_arg)
if (!is.null(eval(parse(text = filter)))) {
df <- df %>%
filter(df[[arg]] %in% input[[filter_arg]])
}
return(df)
}
This is the point where it doesn't work anymore, since it can't find the input while in the function scope - that would be at least my best guess. I though of rewriting function in several ways, these are my ideas:
Have the filer_data.R function take in named arguments for all columns I want to filter. This seems straight-forward but also very redundant to me
Access shiny input variable on the server side, collect all "columns" that start with "filter_" and pass them onto the filter function. The filter function then applies the necessary filters.
I'm pretty sure I mess up somewhere, but I haven't been able to figure it out. What's not working here?