Save state of input shiny - r

Is there good way to save state( dont reset ) of shinyinput which generated on server side?
Example
ui=shinyUI(fluidPage(
selectInput("select","",choices = c(1,2),multiple = T),
uiOutput("din_ui")
))
server=function(input, output,session) {
output$din_ui=renderUI({
lapply(input$select,function(i){
numericInput(inputId = paste0("num_",i),i,"")
})
})
}
shinyApp(ui,server)
If i select 1 in select insert some values into num_1 than add 2 in select than num_1 reset to start value.

You can read the numericInput value, and set the control value at control init. See code:
library(shiny)
ui=shinyUI(fluidPage(
selectInput("select","",choices = c(1,2),multiple = T),
uiOutput("din_ui")
))
server=function(input, output,session) {
output$din_ui=renderUI({
input$select
isolate(
lapply(X = input$select,
FUN = function(i){
cn <- paste0("num_",i)
numericInput(inputId = cn,
label = i,
value = ifelse(!is.null(input[[cn]]), input[[cn]], ''))
}
)
)
})
}
shinyApp(ui,server)

Also find other way using insertUI ( shiny version >=14)
ui=shinyUI(fluidPage(
selectInput("select","",choices = c(1,2),multiple = T),
div(id="din_2")
))
server=function(input, output,session) {
sel_dat=reactiveValues(sel=NULL)
observeEvent(input$select,{
to_add=input$select[!input$select%in%sel_dat$sel]
for ( i in to_add){
insertUI(
selector = '#din_2',
where = "beforeEnd",
ui =numericInput(inputId = paste0("num_",i),i,"")
)
}
to_remove=sel_dat$sel[which(!sel_dat$sel %in% input$select)]
if(length(to_remove)>0){
removeUI(selector = paste0('div:has(>#num_',to_remove,")"))
}
sel_dat$sel=input$select
},ignoreNULL = FALSE)
}

Related

Shiny DT datatable selectInput with reactive data

I recently asked a similar question (Shiny DT datatable input reactivity after table is reloaded). My issue was getting a selectInput in a DT datatable to work correctly after the table is reloaded. The solution worked, which was to use javascript to unbind before reloading the table. However, that example used a static dataframe. When the input data in the datatable are reactive, it doesn't work. In the example below, when the user clicks "Update data" the first time to load data, the selectInput works correctly and input$id1 responds to the user selection. However, when the user clicks "Update data" again to update the reactive data, the input$id no longer responds to the user selection. I've seen two potential approaches to address the issue. One is using dataTableProxy() and replaceData(), and the other is renaming the selectInput ids each time the reactive data are updated. I was wondering if I can avoid those two approaches and get this example working with minimal changes.
require(shiny)
require(DT)
shinyApp(
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());
}
})")
)),
actionButton(inputId = "update", label = "Update data"),
uiOutput("resettable_table")
),
server = function(input, output, session) {
rv <- reactiveValues(
times = 1,
mydata = NULL
)
observeEvent(input$update, {
session$sendCustomMessage("unbindDT", "mytable")
rv$times <- rv$times + 1
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
preDrawCallback = JS('function(){Shiny.unbindAll(this.api().table().node());}'),
drawCallback = JS('function(){Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
output$resettable_table <- renderUI({
req(rv$times)
div(
id = paste0("mydiv", rv$times),
DT::dataTableOutput("mytable")
)
})
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)
Update
Thanks to #StephaneLaurent for pointing out that the reactive counter keeping track of the number of times the data were reloaded was causing the issue. It wasn't actually necessary to put the DT datatable inside a div with an id that updated each time. Here is working code:
require(shiny)
require(DT)
shinyApp(
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());
}
})")
)),
actionButton(inputId = "update", label = "Update data"),
DT::dataTableOutput("mytable")
),
server = function(input, output, session) {
rv <- reactiveValues(mydata = NULL)
observeEvent(input$update, {
session$sendCustomMessage("unbindDT", "mytable")
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
preDrawCallback = JS('function(){Shiny.unbindAll(this.api().table().node());}'),
drawCallback = JS('function(){Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)
The problem is caused by the presence of rv$times in the renderUI. The simplest way to make this app work is to get rid of this renderUI.
However, for fun, and in order to understand what happens, I did the app below which works with the renderUI and which shows what happens. The key point was to remove the id1 element when the table is consecutively rendered two times, before the second rendering. To do so, I use a JavaScript counter i.
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
tags$head(tags$script(
HTML("var i = 1;")
)),
actionButton(inputId = "update", label = "Update data"),
uiOutput("resettable_table")
),
server = function(input, output, session) {
rv <- reactiveValues(
times = 1,
mydata = NULL
)
observeEvent(input$update, {
rv$times <- rv$times + 1
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
initComplete = JS('function(settings) { alert("initComplete - incrementing i"); i++; alert("i = " + i)}'),
preDrawCallback = JS('function() { alert("preDrawCallback triggered - unbinding"); Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { alert("drawCallback triggered - i = " + i); if(i===2) {alert("removing id1 and resetting i to 0"); $("#id1").remove(); i=0;} Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
output$resettable_table <- renderUI({
div(
id = paste0("mydiv", rv$times),
tags$p(paste0("mydiv", rv$times)),
DT::dataTableOutput("mytable")
)
})
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)

Preserve selectizeInput selection in text box after actionButton is used

Default ordering is: Item1, Item2, Item3. If I select a new order and click Update, my selection disappears, but the output table is correct.
How do I preserve my selection so that it shows up like so even after Update is clicked?
library(shiny)
shinyApp(
ui = shinyUI({
fluidPage(
sidebarLayout(
sidebarPanel(uiOutput("selection"),
actionButton('update',"Update")),
mainPanel(
tableOutput('ordered')
)
)
)
}),
server = function(input, output, session) {
values <- reactiveValues(x = c('Item1','Item2','Item3'))
output$selection <- renderUI({
selectizeInput('neworder',
'Select order:',
choices = values$x,
selected = preserve$selection,
multiple = TRUE,
options = list(plugins = list('remove_button', 'drag_drop')))
})
output$ordered <- renderTable(
values$x
)
preserve <- reactiveValues(selection = character())
observeEvent(input$neworder,{
if (!all(preserve$selection %in% input$neworder)) {
preserve$selection = input$neworder
}
})
observeEvent(input$update,{
id <- values$x %in% input$neworder
values$x <- c(input$neworder, values$x[!id])
})
}
)
You can use isolate() to avoid unwanted update:
selectizeInput('neworder',
'Select order:',
choices = isolate(values$x),
selected = preserve$selection,
multiple = TRUE,
options = list(plugins = list('remove_button', 'drag_drop')))

CheckboxInput with Edit table in DT R Shiny

I tried to combine editing table by adding, deleting row in DT table with checkboxInput(). It is not quite correct.
If I didn't add editing feature, it returned correct, but if I added editing feature,it didn't response after I added another row. I got stuck for a while, I will appreciate any help from you guys
library(shiny)
library(shinyjs)
library(DT)
# Tab 2 UI code.
tab2UI <- function(id) {
ns <- NS(id)
tabPanel(
"Tab 2",
fluidRow(
#uiOutput(ns('cars')),
h2('The mtcars data'),
DT::dataTableOutput(ns('mytable2')),
uiOutput(ns("edit_1")),
h2("Selected"),
tableOutput(ns("checked"))
)
)
}
# Tab 2 server code.
tab2Server <- function(input, output, session) {
ns <- session$ns
# Helper function for making checkboxes.
shinyInput = function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(ns(paste0(id, i)), label = NULL, ...))
}
inputs
}
# Update table records with selection.
subsetData <- reactive({
sel <- mtcars[1:5,]
})
values <- reactiveValues(df = NULL)
observe({
values$df <- subsetData()
})
# Datatable with checkboxes.
output$mytable2 <- DT::renderDataTable(
datatable(
data.frame(values$df,Favorite=shinyInput(checkboxInput,nrow(values$df), "cbox_", width = 10)),
editable = TRUE,
selection = 'single',
escape = FALSE,
options = list(
paging = FALSE,
preDrawCallback = JS('function() {Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() {Shiny.bindAll(this.api().table().node()); }')
)
)
)
observeEvent(input$add.row_1,{
# print(paste0("Row selected",input$mytable2_rows_selected))
if (!is.null(input$mytable2_rows_selected)) {
td <- values$df
tid_n = as.numeric(input$mytable2_rows_selected)
tid = as.numeric(input$mytable2_rows_selected) + 1
if(tid_n == nrow(td)){
td<- rbind(data.frame(td[1:tid_n, ]),
data.frame(td[tid_n, ]))
}else{
td<- rbind(data.frame(td[1:tid_n, ]),
data.frame(td[tid_n, ]),
data.frame(td[tid: nrow(td), ]))
}
td <- data.frame(td)
print(td)
values$df <- td
}
})
output$edit_1 <- renderUI({
tagList(
actionButton(inputId = ns("add.row_1"), label = "Add Row", icon = icon("plus"),class = "example-css-selector",style = "background-color:gray; border-color:gray;color:white;height:31px;"),
actionButton(inputId = ns("delete.row_1"), label = "Delete Row", icon = icon("minus"),class = "example-css-selector",style = "background-color:gray; border-color:gray;color:white;height:31px;"),br(),br()
)
})
# Helper function for reading checkbox.
shinyValue = function(id, len) {
values <- unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
return(values)
}
# Output read checkboxes.
observe({
len <- nrow(values$df)
output$checked <- renderTable({
data.frame(selected=shinyValue("cbox_", len))
})
})
}
# Define UI for application.
ui <- fluidPage(
useShinyjs(),
navbarPage(
'Title',
tab2UI("tab2")
)
)
# Define server.
server <- function(input, output, session) {
# Call tab2 server code.
callModule(tab2Server, "tab2")
}
# Run the application
shinyApp(ui = ui, server = server)

Is there a method to using removeUI in for loop?

I'm dynamically generating inputs using a custom function render_panels that creates a wellPanel with a selectizeInput and actionButton contained within, the actionButton removes the entire wellPanel using removeUI by using the id of the div as the selector. I also have a global add button to add new wellPanel.
I have a method to remove the wellPanel by observing the remove button event for each panel, then using removeUI and specifying corresponding div id as selector, but I'm wondering if there is a more efficient method to do this with either for loop or vectorized approach.
Edit Note: Instead of insertUI, I'm specifically using this approach in order to provide the ability to initialize the app with panels already inserted. The shiny app will be executed as a function where users could provide a character vector of dropdown selection values, for example. I've added a character vector prevInputs inside server, a reactive value counter$n which has replaced input$add in order to create initial panels of length(prevInputs) if !is.null(prevInputs) and a method to initialize the selected values argument for selectizeInput with existing values inside make_panels to illustrate the point.
See reprex:
library(shiny)
render_panels <- function(n, removed_panels, inputs){
make_panels <- function(n, inputs){
panels <- tags$div(id = n,
wellPanel(
selectizeInput(inputId = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = inputs[[paste0("dropdown", n)]]),
actionButton(paste0("remove", n), label = paste0("remove", n))
)
)
}
ui_out <- vector(mode = "list", length = n)
for(i in seq_along(ui_out)){
if(i %in% removed_panels) next
ui_out[[i]] <- tagList(
make_panels(n = i, inputs)
)
}
return(ui_out)
}
ui <- fluidPage(
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
removed <- reactiveValues(
values = list()
)
prevInputs <- c("a", "b", "c")
reactiveInputs <- reactiveValues(values = list())
observe({
reactiveInputs$values$dropdown1 = prevInputs[[1]]
reactiveInputs$values$dropdown2 = prevInputs[[2]]
reactiveInputs$values$dropdown3 = prevInputs[[3]]
})
counter <- reactiveValues(n = ifelse(!is.null(prevInputs), length(prevInputs), 0))
observeEvent(input$add, {
counter$n <- counter$n + 1
})
observeEvent(input$remove1,{
removed$values <- c(removed$values, 1)
removeUI(
selector = "div#1", immediate = TRUE,
)
}, once = TRUE)
observeEvent(input$remove2,{
removed$values <- c(removed$values, 2)
removeUI(
selector = "div#2", immediate = TRUE,
)
}, once = TRUE)
observeEvent(input$remove3,{
removed$values <- c(removed$values, 3)
removeUI(
selector = "div#3", immediate = TRUE,
)
}, once = TRUE)
output$mypanels <- renderUI({
render_panels(n = counter$n, removed_panels = removed$values, inputs = reactiveInputs$values)
})
}
shinyApp(ui, server)
As you can see, if there are 100 wellPanels generated, I'd have to use 100 observeEvent, not what we want...here is my attempt at for loop:
I'd like to replace all observeEvent calls with something like below, but cannot seem to get things working.
observe({
req(input$remove1)
for(i in seq_len(input$add)){
if(input[[paste0("remove", i)]] == 1){
removeUI(selector = paste0("div#", i), immediate = TRUE)
}
}
})
Edit:
Here is an attempt from a provided answer using shinymaterial package for alternative UI. Note shinymaterial package requires you to wrap ui elements in render_material_from_server inside renderUI for any UI generated on the server side i.e.
output$dropdown <- renderUI({
render_material_from_server(
material_dropdown(input_id = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = "a")
)
})
This function render_material_from_server is newly available and only exists in current development version of package on GH: shinymaterial
In any case, insertUI does not render UI elements as expected using material_page UI of from shinymaterial
library(shiny)
library(shinymaterial)
make_panels <- function(n, selected){
tags$div(
material_card(
material_dropdown(input_id = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = selected),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- material_page(
tags$script("
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
"),
material_row(
material_column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
choices = c("a", "b", "c")
init_counter <- reactiveVal(3)
observe({
for(i in seq_len(isolate(init_counter()))){
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(i, choices[i]))
}
})
observeEvent(input$add, {
panel_index <- init_counter() + input$add
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(panel_index, choices[panel_index]))
})
}
shinyApp(ui, server)
I think that this situation is a good usecase for modules. Basically, you only write the code once how to generate a panel and then call this module every time you want a new panel. Inside the module, the observeEvent is automatically generated so you don't have to repeat code.
2 things to add:
if you want to access the data returned by the module, you need to store the output of the module call in the main server function
having a lot of modules generates a lot of observers. These observers also stay when a module ui is removed. See this blog post how to deal with this if it should get a problem.
library(shiny)
mod_panel_ui <- function(id) {
ns <- NS(id)
panel_number <- regmatches(id,
regexpr("[0-9]+", id))
tags$div(id = id,
wellPanel(
selectizeInput(inputId = ns("dropdown"),
label = paste0("dropdown ", panel_number),
choices = c("a", "b", "c"),
selected = NULL),
actionButton(ns("remove"), label = paste0("remove ", panel_number))
)
)
}
mod_panel <- function(id) {
moduleServer(id,
function(input, output, session) {
observeEvent(input$remove, {
removeUI(selector = paste0("div#", id))
})
})
return(list(
dropdown = reactive(input$dropdown)
))
}
ui <- fluidPage(
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
div(id = "add_panels_here")
)
)
)
server <- function(input, output, session) {
counter_panels <- 1
observeEvent(input$add, {
current_id <- paste0("panel_", counter_panels)
mod_panel(current_id)
insertUI(selector = "#add_panels_here",
ui = mod_panel_ui(current_id))
# update counter
counter_panels <<- counter_panels + 1
})
}
shinyApp(ui, server)
Edit
Here is a solution that uses shinymaterial and already shows 2 panels on startup. The selected element can be specified by an additional argument to the module server function:
library(shiny)
library(shinymaterial)
mod_panel_ui <- function(id) {
ns <- NS(id)
uiOutput(ns("placeholder"))
}
mod_panel <- function(id, selection = NULL) {
moduleServer(id,
function(input, output, session) {
# generate the UI on the server side
ns <- session$ns
panel_number <- regmatches(id,
regexpr("[0-9]+", id))
output$placeholder <- renderUI({render_material_from_server(tags$div(id = id,
material_card(
material_dropdown(input_id = ns("dropdown"),
label = paste0("dropdown ", panel_number),
choices = c("a", "b", "c"),
selected = selection),
actionButton(ns("remove"), label = paste0("remove ", panel_number))
)
))
})
# remove the element
observeEvent(input$remove, {
removeUI(selector = paste0("div#", id))
})
})
return(list(
dropdown = reactive(input$dropdown)
))
}
ui <- material_page(
material_row(
material_column(width = 6,
actionButton("add", label = "add"),
div(id = "add_panels_here")
)
)
)
server <- function(input, output, session) {
counter_panels <- 1
panels_on_startup <- 2
selected_on_startup <- c("b", "c")
# add counters on startup
lapply(seq_len(panels_on_startup), function(i) {
current_id <- paste0("panel_", counter_panels)
mod_panel(current_id, selected_on_startup[i])
insertUI(selector = "#add_panels_here",
ui = mod_panel_ui(current_id))
# update counter
counter_panels <<- counter_panels + 1
})
observeEvent(input$add, {
current_id <- paste0("panel_", counter_panels)
mod_panel(current_id)
insertUI(selector = "#add_panels_here",
ui = mod_panel_ui(current_id))
# update counter
counter_panels <<- counter_panels + 1
})
}
shinyApp(ui, server)
There is a very simple way to do so if you know some javascript.
There is no need to use for loop
There is no need to save things in a list.
There is no need for renderUI
There is no need to observe every panel
All you need to do is add a js listener to the remove button and add a class in R class = "mybtn" for js to listen to.
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
In your server, you need to think the reverse way, using insertUI rather than removeUI. You only need one observer for the add button. When every time you click on add, add a panel to a div. In my case, I'm lazy, so I just directly select your uiOutput("mypanels")
library(shiny)
make_panels <- function(n){
tags$div(
wellPanel(
selectizeInput(inputId = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = NULL),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- fluidPage(
tags$script("
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
"),
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
observeEvent(input$add, {
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(input$add))
})
observe({
print(input$dropdown5)
})
}
shinyApp(ui, server)
To make sure this works, I add a test observer to watch the dropdown5 (the dropdown when you add the 5th panel). You will see the dropdown value in console once you add the 5th panel.
EDIT for your note:
You can still insert with preset panels. Add a reactive counter for how many panels you want to initiate. Just make sure you isolate the counter and the choice if that is reactive too. In my example choice is hard-coded so I didn't isolate. This is to prevent the panel initialization been run later. The observe I added will only run once.
I also use [] instead of [[]] which gives NA instead of error when out of boundary.
library(shiny)
make_panels <- function(n, selected){
tags$div(
wellPanel(
selectizeInput(inputId = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = selected),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- fluidPage(
tags$script("
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
"),
fluidRow(
column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
choices = c("a", "b", "c")
init_counter <- reactiveVal(3)
observe({
for(i in seq_len(isolate(init_counter()))){
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(i, choices[i]))
}
})
observeEvent(input$add, {
panel_index <- init_counter() + input$add
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(panel_index, choices[panel_index]))
})
}
shinyApp(ui, server)
To work with materialUI:
change the tags$script() to this one
library(shiny)
library(shinymaterial)
make_panels <- function(n, selected){
tags$div(
material_card(
material_dropdown(input_id = paste0("dropdown", n), label = paste0("dropdown", n), choices = c("a", "b", "c"), selected = selected),
actionButton(paste0("remove", n), label = paste0("remove", n), class = "mybtn")
)
)
}
ui <- material_page(
HTML("<script>
$(document).on('click', '.mybtn', function(){
$(this).parent().remove();
})
var formatDropdown = function() {
function initShinyMaterialDropdown(callback) {
$('.shiny-material-dropdown').formSelect();
callback();
}
initShinyMaterialDropdown(function() {
var shinyMaterialDropdown = new Shiny.InputBinding();
$.extend(shinyMaterialDropdown, {
find: function(scope) {
return $(scope).find('select.shiny-material-dropdown');
},
getValue: function(el) {
var ans;
ans = $(el).val();
if (ans === null) {
return ans;
}
if (typeof(ans) == 'string') {
return ans.replace(new RegExp('_shinymaterialdropdownspace_', 'g'), ' ');
} else if (typeof(ans) == 'object') {
for (i = 0; i < ans.length; i++) {
if (typeof(ans[i]) == 'string') {
ans[i] = ans[i].replace(new RegExp('_shinymaterialdropdownspace_', 'g'), ' ');
}
}
return ans;
} else {
return ans;
}
},
subscribe: function(el, callback) {
$(el).on('change.shiny-material-dropdown', function(e) {
callback();
});
},
unsubscribe: function(el) {
$(el).off('.shiny-material-dropdown');
}
});
Shiny.inputBindings.register(shinyMaterialDropdown);
});
}
$(document).ready(function(){
setTimeout(formatDropdown, 500);
})
$(document).on('click', '#add', function(){
setTimeout(formatDropdown, 100);
})
</script>"),
material_row(
material_column(width = 6,
actionButton("add", label = "add"),
uiOutput("mypanels")
)
)
)
server <- function(input, output, session){
choices = c("a", "b", "c")
init_counter <- reactiveVal(3)
observe({
for(i in seq_len(isolate(init_counter()))){
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(i, choices[i]))
}
})
observeEvent(input$add, {
panel_index <- init_counter() + input$add
insertUI(selector = "#mypanels", where = "beforeEnd", ui = make_panels(panel_index, choices[panel_index]))
})
}
shinyApp(ui, server)

User input in DataTable used for recalculation and update of column in Shiny

I want to create a web app, which allows user to enter input in numericInput object, which is embedded in DataTable and recalculates result (multiplication of column with some static values and a user input column) in another column.
I believe that when I set a reactive function which wraps around merging dataset and user input column and later I call it from RenderDataTable, that I somehow break the reactivity and I don't have a clue how to keep reactivity within table dependent on user input (which is also in the table). Please help.
Reproducible example to where I am stuck:
library(shiny)
library(DT)
set.seed(21)
db <- data.frame(ent = rep(x = 1,5),
group = c("G","M","O","F","L"),
val = sample(1:100, 5, replace=TRUE))
ui <- fluidPage(
titlePanel(paste0("entity - ", unique(db$ent))),
sidebarLayout(
sidebarPanel(
helpText("Shiny app calculation")
),
mainPanel(
DT::dataTableOutput("table")
))
)
numericText <- function(FUN, id_nums, id_base, label, value, ...) {
inputs <- 1:length(id_nums)
for (i in 1:length(inputs)) {
inputs[i] <- as.character(FUN(paste0(id_base,
id_nums[i]), label, value, ...))
}
return(inputs)
}
inputs <- numericText(numericInput,
id_nums = as.character(1:5),
id_base = "input_",
label = NULL,
value = 0)
db <- data.frame(db,
num = inputs)
server <- function(input, output, session) {
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
output_table <- reactive({
data.frame(db, calc = shinyValue("input_", 5))
})
output$table <- renderDataTable({
datatable(output_table(), rownames = FALSE, escape = FALSE, selection
= 'none', options = list(paging = FALSE, ordering = FALSE, searching
= FALSE, preDrawCallback = JS('function() {
Shiny.unbindAll(this.api().table().node()); }'), drawCallback =
JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
})
}
shinyApp(ui = ui, server = server)
Also maybe it helps - I was able to do this if I remove reactive expression from the dataframe and if I write result in another output type(however this is not a solution, since my main purpose is to write it in another column in DataTable):
library(shiny)
library(DT)
set.seed(21)
db <- data.frame(ent = rep(x = 1,5),
group = c("G","M","O","F","L"),
val = sample(1:100, 5, replace=TRUE))
ui <- fluidPage(
titlePanel(paste0("entity - ", unique(db$ent))),
sidebarLayout(
sidebarPanel(
helpText("Shiny app calculation")
),
mainPanel(
DT::dataTableOutput("table"),
verbatimTextOutput("text")
))
)
numericText <- function(FUN, id_nums, id_base, label, value, ...) {
inputs <- 1:length(id_nums)
for (i in 1:length(inputs)) {
inputs[i] <- as.character(FUN(paste0(id_base,
id_nums[i]), label, value, ...))
}
return(inputs)
}
inputs <- numericText(numericInput,
id_nums = as.character(1:5),
id_base = "input_",
label = NULL,
value = 0)
db <- data.frame(db,
num = inputs)
server <- function(input, output, session) {
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
output_table <- db
output$table <- renderDataTable({
datatable(output_table, rownames = FALSE, escape = FALSE, selection
= 'none', options = list(paging = FALSE, ordering = FALSE, searching
= FALSE, preDrawCallback = JS('function() {
Shiny.unbindAll(this.api().table().node()); }'), drawCallback =
JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
})
}
output$text <- reactive({shinyValue("input_", 5) * db$val
})
shinyApp(ui = ui, server = server)
I couldn't fully understand your code so I've myself produced another reproducible example based on a bunch of other answers especially this one.
library(shiny)
library(data.table)
library(rhandsontable)
DF = data.frame(num = 1:10, qty = rep(0,10), total = 1:10,
stringsAsFactors = FALSE)
#DF = rbind(DF, c(0,0,0))
ui = fluidPage(
titlePanel("Reactive Table "),
fluidRow(box(rHandsontableOutput("table", height = 400)))
)
server = function(input, output) {
data <- reactiveValues(df=DF)
observe({
input$recalc
data$df <- as.data.frame(DF)
})
observe({
if(!is.null(input$table))
data$df <- hot_to_r(input$table)
})
output$table <- renderRHandsontable({
rhandsontable(data$df)
})
output$table <- renderRHandsontable({
data$df$total <- data$df$num * data$df$qty
print(sum(data$df$num*data$df$price) )
rhandsontable(data$df, selectCallback = TRUE)
})
}
shinyApp(ui, server)
The very first idea is to use rhandsontable which is specifically for this kind of purpose.

Resources