Group headings for groupCheckboxInput - r

In shinywidgets::pickerInput you can pass a named list of data (here, nms) to pickerInput to create headings and choices from the list.
For example:
library(shiny)
library(shinyWidgets)
nms = list('Consumers' = c('a', 'b'),
'Firms' = c('c', 'd'))
ui <- fluidPage(
pickerInput(
inputId = "somevalue",
label = "A label",
choices = nms,
selected = 'a',
multiple=T
),
verbatimTextOutput("value")
)
server <- function(input, output) {
output$value <- renderPrint(input$somevalue)
}
shinyApp(ui, server)
I wish to replicate this heading/choices functionality using shinyWidgets::awesomeCheckboxGroup. Previously I posted this question seeking an answer and was advised that Map could do this. However, Map creates two input objects; I do not require this. The user's choices all need to be fed into a single input object.
Is it possible to create headings in awesomeCheckboxGroup while retaining a single input object?

I looked at the source code and modified awesomeCheckboxGroup and the underlying function generateAwesomeOptions to make it work. Now we can use named lists, which will create sub labels, and unnamed vectors, which will produce the normal checkboxes. We could still optimize the code a bit, and I am also not sure how the labels should look like. But basically you can give them a special class attribute and then use CSS to change the appearance of the labels.
library(shiny)
library(shinyWidgets)
generateAwesomeOptions2 <- function (inputId, choices, selected, inline, status, flag = FALSE) {
# if input is a list, flag will be set to `TRUE` by the calling function
if (flag) {
options <- mapply(choices, names(choices), FUN = function(lchoices, lname) {
lchoices <- shinyWidgets:::choicesWithNames(lchoices)
tags$div(
tags$label(lname, style = "margin-bottom: 10px;"),
mapply(lchoices, names(lchoices), FUN = function(value, name) {
inputTag <- tags$input(type = "checkbox", name = inputId,
value = value, id = paste0(inputId, value))
if (value %in% selected)
inputTag$attribs$checked <- "checked"
if (inline) {
tags$div(class = paste0("awesome-checkbox checkbox-inline checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
# flag is not set `TRUE` this will create the normal checkboxes
else {
tags$div(class = paste0("awesome-checkbox checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)
)
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)} else {
options <- mapply(choices, names(choices), FUN = function(value,
name) {
inputTag <- tags$input(type = "checkbox", name = inputId,
value = value, id = paste0(inputId, value))
if (value %in% selected)
inputTag$attribs$checked <- "checked"
if (inline) {
tags$div(class = paste0("awesome-checkbox checkbox-inline checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
else {
tags$div(class = paste0("awesome-checkbox checkbox-",
status), inputTag, tags$label(name, `for` = paste0(inputId,
value)))
}
}, SIMPLIFY = FALSE, USE.NAMES = FALSE)
}
tags$div(class = "shiny-options-group", options)
}
awesomeCheckboxGroup2 <- function (inputId, label, choices, selected = NULL, inline = FALSE,
status = "primary", width = NULL) {
if(!is.list(choices)) {
choices <- shinyWidgets:::choicesWithNames(choices)
selected <- shiny::restoreInput(id = inputId, default = selected)
if (!is.null(selected))
selected <- shinyWidgets:::validateSelected(selected, choices, inputId)
options <- generateAwesomeOptions2(inputId, choices, selected,
inline, status = status)
} else {
choices2 <- unlist(unname(choices))
choices2 <- shinyWidgets:::choicesWithNames(choices2)
selected <- shiny::restoreInput(id = inputId, default = selected)
if (!is.null(selected))
selected <- shinyWidgets:::validateSelected(selected, choices2, inputId)
options <- generateAwesomeOptions2(inputId, choices, selected,
inline, status = status, flag = TRUE)
}
divClass <- "form-group shiny-input-container shiny-input-checkboxgroup awesome-bootstrap-checkbox"
if (inline)
divClass <- paste(divClass, "shiny-input-container-inline")
awesomeTag <- tags$div(id = inputId, style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), class = divClass,
tags$label(label, `for` = inputId, style = "margin-bottom: 10px;"),
options)
shinyWidgets:::attachShinyWidgetsDep(awesomeTag, "awesome")
}
nms = list('Consumers' = c('a', 'b'),
'Firms' = c('c', 'd'))
nms1 = c("Test", "Test2")
ui <- fluidPage(
awesomeCheckboxGroup2(
inputId = "somevalue",
label = "Make a choice:",
choices = nms
),
verbatimTextOutput("value")
)
server <- function(input, output) {
output$value <- renderPrint(input$somevalue)
}
shinyApp(ui, server)

Related

R Shiny update textInput fields' in DT on changing variable selection

I am building an app where the user loads an .RData data set (the file can be downloaded from here) and selects variable from a list (DT), moves it to another list (also DT) and then the available factor levels are displayed in a third DT underneath. This third DT also has a column of dynamically generated textInput fields which match the number of available factor levels for the variable where the user can add new values for the existing factor levels. The entered values are stored in a reactiveValues object. For now the object is just printed in the R console. The app looks like this:
library(shiny)
library(DT)
library(data.table)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton(id = "recodeChooseSrcFile", label = "Choose data file", title = "Navigate and select a file", multiple = FALSE),
fluidRow(
column(width = 6,
DTOutput(outputId = "recodeAllAvailableVars"),
),
column(width = 1, align = "center",
br(), br(), br(), br(), br(), br(), br(), br(), br(), br(),
uiOutput(outputId = "recodeArrowSelVarsRight"),
br(), br(),
uiOutput(outputId = "recodeArrowSelVarsLeft"),
),
column(width = 5,
DTOutput(outputId = "recodeVarsSelection"),
),
br(), br()
),
br(), br(),
DTOutput(outputId = "recodeScheme")
)
server <- function(input, output, session) {
available.volumes <- getVolumes()()
file.var.recode <- reactiveValues(loaded = NULL, var.levels = NULL)
# Select file and extract the variables.
shinyFileChoose(input, "recodeChooseSrcFile", roots = available.volumes, filetype = list(RData = "RData"))
observeEvent(eventExpr = input$recodeChooseSrcFile, {
if(length(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath) > 0) {
file.var.recode$loaded <- get(load(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath))
file.var.recode$var.levels <- Filter(Negate(is.null), lapply(X = file.var.recode$loaded, FUN = function(i) {
if(is.null(attr(x = i, which = "levels"))) {
NULL
} else {
attr(x = i, which = "levels")
}
}))
file.var.recode$loaded <- data.table(Variables = names(file.var.recode$loaded),
order_col = 1:ncol(file.var.recode$loaded))
}
}, ignoreInit = TRUE)
observe({
var.props.initial.available.vars <- data.table(Variables = as.character(), order_col = as.numeric())
var.props.initial.selected.vars <- data.table(Variables = as.character(), order_col = as.numeric())
recodeAllVars <- reactiveValues(recodeAvailVars = var.props.initial.available.vars, recodeSelectedVars = var.props.initial.selected.vars)
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})
# Render the arrow buttons for selecting the variables.
output$recodeArrowSelVarsRight <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsRight", label = NULL, icon("angle-right"), width = "50px")
}
})
output$recodeArrowSelVarsLeft <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsLeft", label = NULL, icon("angle-left"), width = "50px")
}
})
# Render the data table with the available variables.
output$recodeAllAvailableVars <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeAvailVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Render the table with the selected variables.
output$recodeVarsSelection <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeSelectedVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Observe the events of moving variables from available to selected.
observeEvent(input$recodeArrowSelVarsRight, {
req(input$recodeAllAvailableVars_rows_selected)
recodeAllVars$recodeSelectedVars <- rbind(isolate(recodeAllVars$recodeSelectedVars), recodeAllVars$recodeAvailVars[input$recodeAllAvailableVars_rows_selected, , drop = F])
recodeAllVars$recodeSelectedVars <- recodeAllVars$recodeSelectedVars[complete.cases(recodeAllVars$recodeSelectedVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeAvailVars <- isolate(recodeAllVars$recodeAvailVars[-input$recodeAllAvailableVars_rows_selected, , drop = F])
})
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars), recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
})
# Render the table with the text inputs.
initial.recode.new.values <- reactiveValues(values = NULL)
entered.new.values <- reactiveValues(values = NULL)
new.recoding.values <- reactiveValues(values = NULL)
shinyInput <- function(obj) {
tmp <- unlist(lapply(X = seq_along(obj), FUN = function(i) {
i <- paste0(textInput(inputId = paste0("numinp", i), label = NULL, value = NULL, width = "50px"))
}))
return(tmp)
}
shinyValue <- function(id, len) {
unlist(lapply(seq_len(len), function(i) {
input[[paste0(id, i)]]
}))
}
# Observe the changes in user selection and update the reactive values from above.
observe({
initial.recode.new.values$values <- unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))
entered.new.values$values <- data.table(
V1 = initial.recode.new.values$values,
V2 = initial.recode.new.values$values,
V3 = rep(x = "->", times = length(initial.recode.new.values$values)),
V4 = shinyInput(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))
)
new.recoding.values$values <- shinyValue(id = "numinp", len = length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))))
})
# Render the table with available levels and empty input fields where the user can enter his desired new values.
output$recodeScheme <- renderDT({
if(!is.null(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))) {
entered.new.values$values
} else {
return(NULL)
}
},
rownames = FALSE,
colnames = c("Available variable values", "Old", "->", "New"),
class = "cell-border stripe;compact cell-border;",
selection="none",
escape = FALSE,
options = list(
pageLength = 1500,
dom = 'BRrt',
rowCallback = JS("function(r,d) {$(r).attr('height', '40px')}"),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
observe({
print(new.recoding.values$values)
})
})
}
shinyApp(ui, server)
It all works fine when the variable is selected, the newly entered values are immediately updated and shown in the console on every key stroke. However, if the user decides to remove the variable from the DT of selected ones, the new.recoding.values$values reactive value becomes immediately NULL (as intended), but when another variable is added to the DT of selected variables, the old values for the previous variable are immediately brought back and never get updated. In addition, if the new variable has more levels than the first entered, then the last is possible to update, but not the previous ones (try entering ASBG03, then replace it with ASBG04 to see what I mean).
I don't really understand why is this happening. What I tried so far is to explicitly set the new.recoding.values$values to NULL in:
1.The observer where it is generated, before the shinyValue function is ran.
2.In the observeEvent where the right arrow button is pressed, i.e.:
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars),
recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
new.recoding.values$values <- NULL
})
UPDATE:
3.Following Tonio Liebrand's advice, I tried to update the text inputs as follow (added just after rendering the last DT):
observe({
if(nrow(entered.new.values$values) == 0) {
lapply(seq_len(length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))), function(i) {
updateTextInput(session,
input[[paste0("numinp", i)]],
value = NULL,
label = NULL)
})
}
})
None of these helped. Every time I remove the variable selected at first, the new.recoding.values$values is printed as NULL in the console, but then adding another variable new.recoding.values$values suddenly recovers the first values entered first, like it still "remembers" the first input.
I don't really understand this behavior can someone help to overcome this, i.e. really update on variable change?
Because the textFields are created within the datatable, you need to unbind before you use the table again (updateTextInput doesn't work). Using the code from this answer, I added the JS script with the unbind function and the function is called in the observer for the left arrow. Then you get a working app:
library(shiny)
library(DT)
library(data.table)
library(shinyFiles)
ui <- fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
shinyFilesButton(id = "recodeChooseSrcFile", label = "Choose data file", title = "Navigate and select a file", multiple = FALSE),
fluidRow(
column(width = 6,
DTOutput(outputId = "recodeAllAvailableVars"),
),
column(width = 1, align = "center",
br(), br(), br(), br(), br(), br(), br(), br(), br(), br(),
uiOutput(outputId = "recodeArrowSelVarsRight"),
br(), br(),
uiOutput(outputId = "recodeArrowSelVarsLeft"),
),
column(width = 5,
DTOutput(outputId = "recodeVarsSelection"),
),
br(), br()
),
br(), br(),
DTOutput(outputId = "recodeScheme")
)
server <- function(input, output, session) {
available.volumes <- getVolumes()()
file.var.recode <- reactiveValues(loaded = NULL, var.levels = NULL)
# Select file and extract the variables.
shinyFileChoose(input, "recodeChooseSrcFile", roots = available.volumes, filetype = list(RData = "RData"))
observeEvent(eventExpr = input$recodeChooseSrcFile, {
if(length(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath) > 0) {
file.var.recode$loaded <- get(load(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath))
file.var.recode$var.levels <- Filter(Negate(is.null), lapply(X = file.var.recode$loaded, FUN = function(i) {
if(is.null(attr(x = i, which = "levels"))) {
NULL
} else {
attr(x = i, which = "levels")
}
}))
file.var.recode$loaded <- data.table(Variables = names(file.var.recode$loaded),
order_col = 1:ncol(file.var.recode$loaded))
}
}, ignoreInit = TRUE)
observe({
var.props.initial.available.vars <- data.table(Variables = as.character(), order_col = as.numeric())
var.props.initial.selected.vars <- data.table(Variables = as.character(), order_col = as.numeric())
recodeAllVars <- reactiveValues(recodeAvailVars = var.props.initial.available.vars, recodeSelectedVars = var.props.initial.selected.vars)
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})
# Render the arrow buttons for selecting the variables.
output$recodeArrowSelVarsRight <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsRight", label = NULL, icon("angle-right"), width = "50px")
}
})
output$recodeArrowSelVarsLeft <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsLeft", label = NULL, icon("angle-left"), width = "50px")
}
})
# Render the data table with the available variables.
output$recodeAllAvailableVars <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeAvailVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Render the table with the selected variables.
output$recodeVarsSelection <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeSelectedVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Observe the events of moving variables from available to selected.
observeEvent(input$recodeArrowSelVarsRight, {
req(input$recodeAllAvailableVars_rows_selected)
recodeAllVars$recodeSelectedVars <- rbind(isolate(recodeAllVars$recodeSelectedVars), recodeAllVars$recodeAvailVars[input$recodeAllAvailableVars_rows_selected, , drop = F])
recodeAllVars$recodeSelectedVars <- recodeAllVars$recodeSelectedVars[complete.cases(recodeAllVars$recodeSelectedVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeAvailVars <- isolate(recodeAllVars$recodeAvailVars[-input$recodeAllAvailableVars_rows_selected, , drop = F])
})
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars), recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
session$sendCustomMessage("unbindDT", "recodeScheme")
})
# Render the table with the text inputs.
initial.recode.new.values <- reactiveValues(values = NULL)
entered.new.values <- reactiveValues(values = NULL)
new.recoding.values <- reactiveValues(values = NULL)
shinyInput <- function(obj) {
tmp <- unlist(lapply(X = seq_along(obj), FUN = function(i) {
i <- paste0(textInput(inputId = paste0("numinp", i), label = NULL, value = NULL, width = "50px"))
}))
return(tmp)
}
shinyValue <- function(id, len) {
unlist(lapply(seq_len(len), function(i) {
input[[paste0(id, i)]]
}))
}
# Observe the changes in user selection and update the reactive values from above.
observe({
initial.recode.new.values$values <- unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))
entered.new.values$values <- data.table(
V1 = initial.recode.new.values$values,
V2 = initial.recode.new.values$values,
V3 = rep(x = "->", times = length(initial.recode.new.values$values)),
V4 = shinyInput(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))
)
new.recoding.values$values <- shinyValue(id = "numinp", len = length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))))
})
# Render the table with available levels and empty input fields where the user can enter his desired new values.
output$recodeScheme <- renderDT({
if(!is.null(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))) {
entered.new.values$values
} else {
return(NULL)
}
},
rownames = FALSE,
colnames = c("Available variable values", "Old", "->", "New"),
class = "cell-border stripe;compact cell-border;",
selection="none",
escape = FALSE,
options = list(
pageLength = 1500,
dom = 'BRrt',
rowCallback = JS("function(r,d) {$(r).attr('height', '40px')}"),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
observe({
print(new.recoding.values$values)
})
})
}
shinyApp(ui, server)
However, I recommend you to read more about reactivity, e.g. here. You use a lot of observers, and you nest them. I don't recommend that, because this can lead to strange behaviour. Also, try to use more reactive/reactiveExpression, because observe/observeEvent can make your app slower. Before I found the correct solution, I tried to unnest your code a bit, and it still works! That shows that you had complexity in your app you actually don't need:
library(shiny)
library(DT)
library(data.table)
library(shinyFiles)
# additional functions
shinyInput <- function(obj) {
tmp <- unlist(lapply(X = seq_along(obj), FUN = function(i) {
i <- paste0(textInput(inputId = paste0("numinp", i), label = NULL, value = NULL, width = "50px"))
}))
return(tmp)
}
shinyValue <- function(id, len, input) {
unlist(lapply(seq_len(len), function(i) {
input[[paste0(id, i)]]
}))
}
ui <- fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
shinyFilesButton(id = "recodeChooseSrcFile", label = "Choose data file", title = "Navigate and select a file", multiple = FALSE),
fluidRow(
column(width = 6,
DTOutput(outputId = "recodeAllAvailableVars"),
),
column(width = 1, align = "center",
br(), br(), br(), br(), br(), br(), br(), br(), br(), br(),
uiOutput(outputId = "recodeArrowSelVarsRight"),
br(), br(),
uiOutput(outputId = "recodeArrowSelVarsLeft"),
),
column(width = 5,
DTOutput(outputId = "recodeVarsSelection"),
),
br(), br()
),
br(), br(),
DTOutput(outputId = "recodeScheme")
)
server <- function(input, output, session) {
available.volumes <- getVolumes()()
file.var.recode <- reactiveValues(loaded = NULL, var.levels = NULL)
# define variables
# Render the table with the text inputs.
initial.recode.new.values <- reactiveValues(values = NULL)
entered.new.values <- reactiveValues(values = NULL)
new.recoding.values <- reactiveValues(values = NULL)
# Select file and extract the variables.
shinyFileChoose(input, "recodeChooseSrcFile", roots = available.volumes, filetype = list(RData = "RData"))
observeEvent(eventExpr = input$recodeChooseSrcFile, {
if(length(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath) > 0) {
file.var.recode$loaded <- get(load(parseFilePaths(available.volumes, input$recodeChooseSrcFile)$datapath))
file.var.recode$var.levels <- Filter(Negate(is.null), lapply(X = file.var.recode$loaded, FUN = function(i) {
if(is.null(attr(x = i, which = "levels"))) {
NULL
} else {
attr(x = i, which = "levels")
}
}))
file.var.recode$loaded <- data.table(Variables = names(file.var.recode$loaded),
order_col = 1:ncol(file.var.recode$loaded))
}
}, ignoreInit = TRUE)
recodeAllVars <- reactiveValues(recodeAvailVars = data.table(Variables = as.character(), order_col = as.numeric()),
recodeSelectedVars = data.table(Variables = as.character(), order_col = as.numeric()))
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})
# Render the arrow buttons for selecting the variables.
output$recodeArrowSelVarsRight <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsRight", label = NULL, icon("angle-right"), width = "50px")
}
})
output$recodeArrowSelVarsLeft <- renderUI({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
actionButton(inputId = "recodeArrowSelVarsLeft", label = NULL, icon("angle-left"), width = "50px")
}
})
# Render the data table with the available variables.
output$recodeAllAvailableVars <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeAvailVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Render the table with the selected variables.
output$recodeVarsSelection <- renderDT({
if(is.null(file.var.recode$loaded)) {
return(NULL)
} else {
setkeyv(x = recodeAllVars$recodeSelectedVars, cols = "order_col")
}
},
caption = "Available variables",
rownames = FALSE,
colnames = c("Names", "sortingcol"),
options = list(
ordering = FALSE,
columnDefs = list(list(visible = FALSE, targets = 1))
))
# Observe the events of moving variables from available to selected.
observeEvent(input$recodeArrowSelVarsRight, {
req(input$recodeAllAvailableVars_rows_selected)
recodeAllVars$recodeSelectedVars <- rbind(isolate(recodeAllVars$recodeSelectedVars), recodeAllVars$recodeAvailVars[input$recodeAllAvailableVars_rows_selected, , drop = F])
recodeAllVars$recodeSelectedVars <- recodeAllVars$recodeSelectedVars[complete.cases(recodeAllVars$recodeSelectedVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeAvailVars <- isolate(recodeAllVars$recodeAvailVars[-input$recodeAllAvailableVars_rows_selected, , drop = F])
})
observeEvent(input$recodeArrowSelVarsLeft, {
req(input$recodeVarsSelection_rows_selected)
recodeAllVars$recodeAvailVars <- rbind(isolate(recodeAllVars$recodeAvailVars), recodeAllVars$recodeSelectedVars[input$recodeVarsSelection_rows_selected, , drop = F])
recodeAllVars$recodeAvailVars <- recodeAllVars$recodeAvailVars[complete.cases(recodeAllVars$recodeAvailVars[ , "Variables"]), , drop = FALSE]
recodeAllVars$recodeSelectedVars <- isolate(recodeAllVars$recodeSelectedVars[-input$recodeVarsSelection_rows_selected, , drop = F])
session$sendCustomMessage("unbindDT", "recodeScheme")
})
# Observe the changes in user selection and update the reactive values from above.
observe({
initial.recode.new.values$values <- unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))
entered.new.values$values <- data.table(
V1 = initial.recode.new.values$values,
V2 = initial.recode.new.values$values,
V3 = rep(x = "->", times = length(initial.recode.new.values$values)),
V4 = shinyInput(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))
)
new.recoding.values$values <- shinyValue(id = "numinp",
len = length(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]]))),
input = input)
})
# Render the table with available levels and empty input fields where the user can enter his desired new values.
output$recodeScheme <- renderDT({
if(!is.null(unlist(unique(file.var.recode$var.levels[recodeAllVars$recodeSelectedVars[ , Variables]])))) {
entered.new.values$values
} else {
return(NULL)
}
},
rownames = FALSE,
colnames = c("Available variable values", "Old", "->", "New"),
class = "cell-border stripe;compact cell-border;",
selection="none",
escape = FALSE,
options = list(
pageLength = 1500,
dom = 'BRrt',
rowCallback = JS("function(r,d) {$(r).attr('height', '40px')}"),
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
observe({
print(new.recoding.values$values)
})
# end of server
}
shinyApp(ui, server)
There is still some room for improvement, e.g. you could try to use a reactive instead of observe for the following snippet:
# Observe if the file is loaded, and if yes, update the table of available variables.
observe({
if(!is.null(file.var.recode$loaded)) {
recodeAllVars$recodeAvailVars <- file.var.recode$loaded
}
})

How to add new row in R Shiny dataTable without refreshing the existing selected data variable in table

I am trying to create a data filter using R Shiny and DataTables, So far I am able to add the data and when I click on Add New Filter after selecting the variables , The selected variable values is refreshed.
Please help me so when I add new row , the selected variable remains same.
Below is the code:
server.r
library(shiny)
library(shinyBS)
library(shinyjs)
library(DT)
library(rhandsontable)
shinyServer(function(input, output, session) {
myValue <- reactiveValues()
observe({
if(is.null(myValue$Filter)){
Choices<- c("a","b","c","d")
myValue$Filter = data.frame(
Variable = shinyInput(selectInput, 1:5, 'var_', cl = "dynamicInputs", label = "", choices = Choices ,selected=Choices[1]),
Filter = shinyInput(actionButton, 1:5, 'go_button_', cl=NULL, label = "Filter", onclick = goButtonOnClick()) ,
Logic = c(NA,shinyInput(selectInput, 2:5, 'logic_', cl = "dynamicInputs",label = "", choices = c("And","Or"))),
Remove = c(NA,shinyInput(actionButton, 2:5, 'remove_button_',cl=NULL, "", icon = icon("close"), onclick = removeButtonOnClick())),
stringsAsFactors = FALSE,
row.names = 1:5
)
maxId<<-5
}
})
#Functions
shinyInput <- function(FUN, objs, id,cl= NULL, ...) {
inputs <- character(length(objs))
if(is.null(cl)){
for (i in 1:length(objs)) {
inputs[i] <- as.character(FUN(paste0(id, objs[i]), ...))
}
}
else {
for (i in 1:length(objs)) {
inputs[i] <- as.character(div(class=cl,FUN(paste0(id, objs[i]), ...)))
}
}
inputs
}
goButtonOnClick <- function() {
return('Shiny.onInputChange(\"select_button\", this.id) ;
Shiny.setInputValue(\"filter_button_change\", Math.random()) ')
}
removeButtonOnClick <- function() {
return('Shiny.onInputChange(\"select_remove_button\", this.id)')
}
extractValues<-function(idSeq,objName){
unlist(lapply(idSeq,function(id){
selected<-as.numeric(id)
return(as.character(shinyValue(objName, selected)))
}))
}
# Add new Condition Row
observeEvent(input$addnewRow,{
if(is.null(myValue$Filter) || nrow(myValue$Filter)==0)
return()
idSeq<-unlist(lapply(myValue$Filter$Filter,function(x){
buttonId<-stringr::str_extract(x,paste0("go_button_","\\d+"))
num<-as.numeric(gsub("go_button_","",buttonId))
return(num)
}))
allVars<-extractValues(idSeq,"var_")
allLogic<-extractValues(idSeq,"logic_")
Choices<- c("a","b","c","d")
Variable<-unlist(lapply(1:length(idSeq),function(i){
shinyInput(selectInput, idSeq[i], 'var_', cl = "dynamicInputs", label = "", choices = Choices ,selected=allVars[i])
}))
Logic<-unlist(lapply(1:length(idSeq),function(i){
if(is.na(allLogic[i]))
return(NA)
shinyInput(selectInput, idSeq[i], 'logic_', cl = "dynamicInputs",label = "", choices = c("And","Or"),selected=allLogic[i])
}))
myValue$Filter$Variable<-Variable
myValue$Filter$Logic<-Logic
maxId<<-maxId+1
newRow <- data.frame(
Variable = shinyInput(selectInput, maxId, 'var_', cl = "dynamicInputs", label = "", choices = Choices),
Filter = shinyInput(actionButton, maxId, 'go_button_',cl=NULL, label = "Filter", onclick = goButtonOnClick() ),
Logic = shinyInput(selectInput, maxId, 'logic_',cl = "dynamicInputs", label = "", choices = c("And","Or")),
Remove = shinyInput(actionButton, maxId, 'remove_button_',cl=NULL, "", icon = icon("close"), onclick = removeButtonOnClick() ),
stringsAsFactors = FALSE
)
rownames(newRow) <- as.character(nrow(myValue$Filter)+1)
myValue$Filter <- rbind(myValue$Filter,newRow)
})
# Remove Row
observeEvent(input$select_remove_button,{
if(is.null(myValue$Filter) || nrow(myValue$Filter)==0)
return()
idSeq<-unlist(lapply(myValue$Filter$Filter,function(x){
buttonId<-stringr::str_extract(x,paste0("go_button_","\\d+"))
num<-as.numeric(gsub("go_button_","",buttonId))
return(num)
}))
allVars<-extractValues(idSeq,"var_")
allLogic<-extractValues(idSeq,"logic_")
Choices <- c("a","b","c","d")
Variable<-unlist(lapply(1:length(idSeq),function(i){
shinyInput(selectInput, idSeq[i], 'var_', cl = "dynamicInputs", label = "", choices = Choices ,selected=allVars[i])
}))
Logic<-unlist(lapply(1:length(idSeq),function(i){
if(is.na(allLogic[i]))
return(NA)
shinyInput(selectInput, idSeq[i], 'logic_', cl = "dynamicInputs",label = "", choices = c("And","Or"),selected=allLogic[i])
}))
myValue$Filter$Variable<-Variable
myValue$Filter$Logic<-Logic
rowToRemove<- which(unlist(lapply(myValue$Filter$Remove,function(x){stringr::str_extract(x,paste0("remove_button_","\\d+"))}))==input$select_remove_button)
myValue$Filter<-myValue$Filter[-rowToRemove,]
if(nrow(myValue$Filter)>0)
rownames(myValue$Filter)<-as.character(1:nrow(myValue$Filter))
})
# Display bsModal for filter
observeEvent(input$filter_button_change, {
toggleModal(session,"CustomDataFilter",toggle="open")
})
# helper function for reading selectinput
shinyValue = function(id, objs) {
unlist(lapply(objs, function(i) {
value = input[[paste0(id, i)]]
if (is.null(value))
NA
else{
value
}
}))
}
# Show Filter Table
output$mytable = DT::renderDataTable({
if(is.null(myValue$Filter))
return()
myValue$Filter
}, selection = 'none', server = FALSE, escape = FALSE, options = list(
dom = "ti",
paging = TRUE,
preDrawCallback = JS(
'function() {
Shiny.unbindAll(this.api().table().node()); }'
),
drawCallback = JS('function() {
Shiny.bindAll(this.api().table().node()); } ')
))
})
ui.r
library(shiny)
library(shinyBS)
library(shinyjs)
library(DT)
library(rhandsontable)
shinyUI(fluidPage(
tags$head(tags$script(HTML("
$(document).on('change', '.dynamicInputs .selector select', function(){
Shiny.setInputValue('lastSelectId', this.id, {priority: 'event'});
});
"))),
tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbind-DT', function(id) {
Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
})"
)
),
tags$button(
id = "reset_button",
class="btn action-button",
icon("close")
),
bsModal("CustomDataFilter","Settings","go_CustomDataFilter_Settings",size="large",
uiOutput("FilterDataSettings")
),
bsModal("CustomDataFormula","Settings","go_CustomDataFormula_Settings",size="large",
uiOutput("CustomDataFormula")
),
actionButton("addnewRow"," Add New Filter "),
DT::dataTableOutput('mytable')
)
)

R Shiny - unexpected behaviour of updateSelectizeInput with observer

I have a selectizeInput that can take multiple values (here: names of datasets). The current state of this input is monitored by an observeEvent, which renders the corresponding datatables and dynamically populates a tabsetPanel with the outputs. It all works fine when I choose new values directly in the input field. However, when I supply multiple new values with the updateSelectizeInput function, all tabs contain the same dataframe corresponding to the last value in the selected argument.
The example below illustrates the problem. The UI reacts as expected when using the input field, but when pressing the "Add all at once" button all tabs contain the same dataframe.
library(shiny)
library(shinyWidgets)
library(shinyjs)
library(DT)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "dataframes", label = "select dataframes", choices = c("iris", "mtcars", "DNase", "ChickWeight"), multiple = TRUE),
actionButton(inputId = "add_all", label = "Add all at once")
),
mainPanel(tabsetPanel(id = "df_tabset"))
)
)
server <- function(input, output, session) {
tables <- reactiveValues(iris = iris, mtcars = mtcars, DNase = DNase, ChickWeight = ChickWeight,
df_tabset = NULL) # keeps track of currently displayed tables
observeEvent(input$dataframes, {
if (length(input$dataframes) > length(tables$df_tabset)) { # new dataframes are selected
new_dfs = setdiff(input$dataframes, tables$df_tabset)
for(df in new_dfs){
output[[df]] = renderDT(tables[[df]], editable = T, rownames = F, options = list(dom = "t")) # DOES NOT WORK AS EXPECTED IF THERE is > 1 NEW DF
appendTab(inputId = "df_tabset", select = TRUE,
tabPanel(title = df, value = df, DTOutput(outputId = df))
)
}
tables$df_tabset = input$dataframes # update
} else {
df = tables$df_tabset[! tables$df_tabset %in% input$dataframes]
removeTab(inputId = "df_tabset", target = df)
tables$df_tabset = input$dataframes
}
}, ignoreNULL = FALSE, ignoreInit = TRUE)
observeEvent(input$add_all, {
updateSelectizeInput(session, "dataframes", selected = c("iris", "mtcars", "DNase", "ChickWeight"))
})
}
shinyApp(ui = ui, server = server)
You have to use local (see here).
observeEvent(input$dataframes, {
if (length(input$dataframes) > length(tables$df_tabset)) { # new dataframes are selected
new_dfs = setdiff(input$dataframes, tables$df_tabset)
for(df in new_dfs){
local({
.df <- df
output[[.df]] = renderDT(tables[[.df]], editable = TRUE,
rownames = FALSE, options = list(dom = "t"))
})
appendTab(inputId = "df_tabset", select = TRUE,
tabPanel(title = df, value = df, DTOutput(outputId = df))
)
}
tables$df_tabset = input$dataframes # update
} else {
df = tables$df_tabset[! tables$df_tabset %in% input$dataframes]
removeTab(inputId = "df_tabset", target = df)
tables$df_tabset = input$dataframes
}
}, ignoreNULL = FALSE, ignoreInit = TRUE)

Sorting a named list using actionButton Shiny

I have a named list as follows:
vegshop <- list(
"FRUITS" = c("MANGO", "JACKFRUIT", "BANANA"),
'VEGETABLES' = c("OKRA", "BEANS", "CABBAGE")
)
I am trying to order the list based on the names, and this works fine.
vegshop[order(names(vegshop), decreasing = F)]
However when I try to do using an actionButton(), I am getting the following error:
the condition has `length > 1` and only the first element will be used
or
Warning: Error in order: unimplemented type 'list' in 'orderVector1'
A workable example is as follows:
vegshop <- list(
"FRUITS" = c("MANGO", "JACKFRUIT", "BANANA"),
'VEGETABLES' = c("OKRA", "BEANS", "CABBAGE")
)
grocer <- list(
"GROCERY" = c("CEREALS", "PULSES", "TOILETRIES"),
"CLEANERS" = c("DETERGENTS", "FLOOR CLEANERS", "WIPES")
)
library(shiny)
ui <- shinyUI(
fluidPage(
actionButton(style = "font-size: 10px;",inputId = "a2z", label = "Sort-A-Z", icon = icon("sort-alpha-asc")),
radioButtons(inputId = "shopsel", label = "SELECT SHOP", choices = c("SHOPS","SUPERMARKETS"), selected = "SHOPS", inline = TRUE),
uiOutput("shoplist")))
server <- function(session,input, output) {
output$shoplist <- renderUI({
if(input$shopsel == "SHOPS") {
selectInput(inputId = "vegShopList", label = "SHOPLIST", choices = vegshop, selected = c('MANGO', 'JACKFRUIT', 'BANANA'), multiple = TRUE, selectize = FALSE)
} else if(input$shopsel == "SUPERMARKETS") {
selectInput(inputId = "smList", label = "SUPERMARKET", choices = grocer, selected = c('CEREALS', 'PULSES', 'TOILETRIES'), multiple = TRUE, selectize = FALSE)
}
})
observeEvent(input$a2z, {
if(input$shopsel == "SHOPS") {
updateSelectInput(session, inputId = "vegShopList", choices = vegshop[order(vegshop), decreasing = F], selected = NULL)
} else if(input$shopsel == "SUPERMARKETS") {
updateSelectInput(session, inputId = "smList", choices = grocer[order(grocer), decreasing = F], selected = NULL)
}
})
}
shinyApp(ui = ui, server = server)
How could I get the list sorted by the names using the actionButton().
You have a typo:
Outside shiny you write:
vegshop[order(names(vegshop), decreasing = F)]
Within shiny:
vegshop[order(vegshop), decreasing = F]
The same probably holds for the following shiny code snippet:
grocer[order(grocer), decreasing = F]

How to get user input from an output object

I am creating a shiny app that dynamically creates a set of input boxes, the input from those boxes is then used to further create another set of input boxes.
First I get input from the "number of questions" text box and dynamically add that number of "question panels" to the ui (example below).
Issue
The issue is that the generated objects are always attached to the output, and I can't figure out how to grab new user input from them. How could I get this input, and then generate a second round of "answer" input boxes?
ui.R
shinyUI(fluidPage(
titlePanel("RSurvey"),
numericInput("questionCountText", label = h3("Number of Questions"), value = 1),
uiOutput("questionSet")
))
server.R
shinyServer(
function(input, output) {
output[['questionSet']] <- renderUI({
outputHtml = ""
count = input$questionCountText
if(count > 0) {
for(i in 1:input$questionCountText) {
outputHtml = paste0(outputHtml, questionPanel(i))
}
}
HTML(outputHtml)
})
}
)
questionPanel = function(i)
{
return(wellPanel(div(style="display:inline-block", textInput("questionText", label = h4(paste0("Question ", i)), "Enter your question")),
numericInput1("answerCountText", label = h4("Number of Answers"), value = 3, onchange="onTextChanged(this.value)")))
}
numericInput1 = function (inputId, label, value = "", ...)
{
div(style="display:inline-block",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "numeric", value = value, ...))
}
Hello try this for example :
#ui
ui <- fluidPage(
titlePanel("RSurvey"),
numericInput("questionCountText", label = h3("Number of Questions"), value = 1),
uiOutput("questionSet"),
verbatimTextOutput(outputId = "answers")
)
#server
server <- function(input, output) {
output[['questionSet']] <- renderUI({
outputHtml = ""
count = input$questionCountText
if(count > 0) {
for(i in 1:input$questionCountText) {
outputHtml = paste0(outputHtml, questionPanel(i))
}
}
HTML(outputHtml)
})
output$answers <- renderPrint({
invisible(
sapply(
X = seq_len(input$questionCountText),
FUN = function(i) {
cat(paste0("Question", i, "\n", input[[paste0("questionText", i)]], "\n", input[[paste0("answerCountText", i)]], "\n"))
}
)
)
})
}
#app
shinyApp(ui = ui, server = server)
#utils
questionPanel = function(i) {
wellPanel(
div(
style="display:inline-block",
textInput2(inputId = paste0("questionText", i), label = h4(paste0("Question ", i)), placeholder = "Enter your question")
),
numericInput1(inputId = paste0("answerCountText", i), label = h4("Number of Answers"), value = 3, onchange="onTextChanged(this.value)")
)
}
numericInput1 = function (inputId, label, value = "", ...) {
div(style="display:inline-block", class = "form-group shiny-input-container",
tags$label(label, `for` = inputId),
tags$input(id = inputId, type = "number", value = value, ...))
}
`%AND%` <- shiny:::`%AND%`
textInput2 <- function (inputId, label, value = "", placeholder = NULL, width = NULL)
{
if (is.null(placeholder)) {
div(class = "form-group shiny-input-container", style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), label %AND%
tags$label(label, `for` = inputId), tags$input(id = inputId,
type = "text", class = "form-control", value = value))
} else {
div(class = "form-group shiny-input-container", style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), label %AND%
tags$label(label, `for` = inputId), tags$input(id = inputId, placeholder = placeholder,
type = "text", class = "form-control", value = value))
}

Resources