Related
I am working on a shiny application that allows users to enter comments about an observation. The comments are then saved in a SQL database on the back end. The code below is a working representation of my current application.
What is happening is the tables load with the subset of Cylinder = 4 (the radio buttons), the user can save comments, got to Cylinder = 6, save comments, and then Cylinder = 8, and save comments. But if I ever change the cylinder back to a value that I've already saved comments at, the text inputs are unbound and no comments are saved. In order to restore the functionality, I have to restart the application. I've found that irritates my users.
What do I need to do to make sure I can continue to save comments if I go back to a Cylinder value I've already used?
I'm sorry that it isn't a very concise example. When you enter a comment, the console will print the number of comments saved, and display the data frame that was altered so you can compare what is showing in the application.
library(shiny)
library(DT)
library(dplyr)
mtcars$comment <- rep("", nrow(mtcars))
mtcars$row_id <- seq_len(nrow(mtcars))
AppData <- split(mtcars, mtcars[c("cyl", "am")])
# Makes a text input column out of a data frame
make_inputtable <- function(df){
df$comment <-
mapply(
function(comment, id){
as.character(textInput(inputId = sprintf("txt_comment_%s", id),
label = "",
value = comment))
},
comment = df$comment,
id = df$row_id,
SIMPLIFY = TRUE)
df
}
ui <- shinyUI(
fluidPage(
radioButtons(inputId = "rdo_cyl",
label = "Cylinders",
choices = sort(unique(mtcars$cyl)),
inline = TRUE),
h3("Automatic"),
actionButton(inputId = "btn_save_automatic",
label = "Save Comments"),
DT::dataTableOutput("am0"),
hr(),
h3("Manual"),
actionButton(inputId = "btn_save_manual",
label = "Save Comments"),
DT::dataTableOutput("am1"),
# unbind a datatable. Needs to be done before a table is redrawn.
tags$script(HTML(
"Shiny.addCustomMessageHandler('unbind-DT', function(id) {
Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
})"))
)
)
server <- shinyServer(function(input, output, session){
reactiveData <- reactiveValues(
am0_cyl4 = AppData[["4.0"]],
am0_cyl6 = AppData[["6.0"]],
am0_cyl8 = AppData[["8.0"]],
am1_cyl4 = AppData[["4.1"]],
am1_cyl6 = AppData[["6.1"]],
am1_cyl8 = AppData[["8.1"]]
)
# Reactive Objects ------------------------------------------------
ref0 <- reactive({
sprintf("am0_cyl%s", input$rdo_cyl)
})
data0 <- reactive({
reactiveData[[ref0()]]
})
ref1 <- reactive({
sprintf("am1_cyl%s", input$rdo_cyl)
})
data1 <- reactive({
reactiveData[[ref1()]]
})
# Event Observers -------------------------------------------------
observeEvent(
input$btn_save_automatic,
{
in_field <- names(input)[grepl("^txt_comment_", names(input))]
in_field_id <- sub("^txt_comment_", "", in_field)
in_field_id <- as.numeric(in_field_id)
in_field_id <- in_field_id[in_field_id %in% data0()$row_id]
exist_frame <- data0()[c("row_id", "comment")]
new_frame <-
data.frame(
row_id = in_field_id,
comment = vapply(in_field_id,
function(id){ input[[sprintf("txt_comment_%s", id)]]},
character(1)),
stringsAsFactors = FALSE)
Compare <- left_join(exist_frame,
new_frame,
by = "row_id",
suffix = c("_exist", "_new")) %>%
filter(comment_exist != comment_new)
message(sprintf("* %s comment(s) saved", nrow(Compare)))
# Only perform the save operations if there are changes to be made.
if (nrow(Compare)){
session$sendCustomMessage("unbind-DT", "am0")
for(i in seq_len(nrow(Compare))){
row <- Compare$row_id
reactiveData[[ref0()]]$comment[reactiveData[[ref0()]]$row_id == row] <-
input[[sprintf("txt_comment_%s", row)]]
}
print(data0())
}
}
)
# Very similar to btn_save_automatic
observeEvent(
input$btn_save_manual,
{
in_field <- names(input)[grepl("^txt_comment_", names(input))]
in_field_id <- sub("^txt_comment_", "", in_field)
in_field_id <- as.numeric(in_field_id)
in_field_id <- in_field_id[in_field_id %in% data1()$row_id]
exist_frame <- data1()[c("row_id", "comment")]
new_frame <-
data.frame(
row_id = in_field_id,
comment = vapply(in_field_id,
function(id){ input[[sprintf("txt_comment_%s", id)]]},
character(1)),
stringsAsFactors = FALSE)
Compare <- left_join(exist_frame,
new_frame,
by = "row_id",
suffix = c("_exist", "_new")) %>%
filter(comment_exist != comment_new)
message(sprintf("* %s comment(s) saved", nrow(Compare)))
# Only perform the save operations if there are changes to be made.
if (nrow(Compare)){
session$sendCustomMessage("unbind-DT", "am1")
for(i in seq_len(nrow(Compare))){
row <- Compare$row_id
reactiveData[[ref1()]]$comment[reactiveData[[ref1()]]$row_id == row] <-
input[[sprintf("txt_comment_%s", row)]]
}
print(data1())
}
}
)
# Output Objects --------------------------------------------------
output$am0 <-
DT::renderDataTable({
make_inputtable(data0()) %>%
datatable(escape = -13,
options = list(preDrawCallback = DT::JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = DT::JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
})
output$am1 <-
DT::renderDataTable({
make_inputtable(data1()) %>%
datatable(escape = -13,
options = list(preDrawCallback = DT::JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = DT::JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
})
})
shinyApp(ui = ui, server = server)
Edits and updates
editable data tables are a potential solution, but would require upgrading our package library. We are currently using R 3.4.1 with shiny 1.0.4 and DT 0.2.12.
Yes, that's comparatively ancient. But the cost of upgrading is substantial given the sensitivity of the reports supported by this application and the quality assurance required by any upgrade.
Putting aside your version restrictions, here is how I'd approach this with the latest library(DT) version (Hopefully useful for future readers and maybe someday you will also update):
Edit: now using dataTableProxy to avoid re-rendering.
library(shiny)
library(DT)
ui <- shinyUI(
fluidPage(
radioButtons(inputId = "rdo_cyl",
label = "Cylinders",
choices = sort(unique(mtcars$cyl)),
inline = TRUE),
h3("Automatic"),
actionButton(inputId = "btn_save_automatic",
label = "Save Comments"), p(),
DTOutput("am0"),
hr(),
h3("Manual"),
actionButton(inputId = "btn_save_manual",
label = "Save Comments"), p(),
DTOutput("am1")
)
)
server <- shinyServer(function(input, output, session){
globalData <- mtcars
globalData$comment <- rep("", nrow(mtcars))
globalData$row_id <- seq_len(nrow(mtcars))
diabledCols <- grep("comment", names(globalData), invert = TRUE)
AppData <- reactiveVal(globalData)
automaticAppData <- reactive({
AppData()[AppData()[["cyl"]] %in% input$rdo_cyl & AppData()[["am"]] %in% "0", ]
})
manualAppData <- reactive({
AppData()[AppData()[["cyl"]] %in% input$rdo_cyl & AppData()[["am"]] %in% "1", ]
})
output$am0 <- DT::renderDT(
# isolate: render only once
expr = {isolate(automaticAppData())},
editable = list(target = "cell", disable = list(columns = diabledCols))
)
output$am1 <- DT::renderDT(
# isolate: render only once
expr = {isolate(manualAppData())},
editable = list(target = "cell", disable = list(columns = diabledCols))
)
observeEvent(input$btn_save_automatic, {
info = input$am0_cell_edit
str(info)
i = automaticAppData()$row_id[[info$row]]
j = info$col
v = info$value
globalData[i, j] <<- DT::coerceValue(v, globalData[i, j])
AppData(globalData)
# update database...
})
observeEvent(input$btn_save_manual, {
info = input$am1_cell_edit
str(info)
i = manualAppData()$row_id[[info$row]]
j = info$col
v = info$value
globalData[i, j] <<- DT::coerceValue(v, globalData[i, j])
AppData(globalData)
# update database...
})
am0Proxy <- dataTableProxy("am0")
am1Proxy <- dataTableProxy("am1")
observeEvent(automaticAppData(), {
replaceData(am0Proxy, automaticAppData(), resetPaging = FALSE)
})
observeEvent(manualAppData(), {
replaceData(am1Proxy, manualAppData(), resetPaging = FALSE)
})
})
shinyApp(ui = ui, server = server)
Here are some related infos.
Update for DT Version 0.2
Here is another solution closer to your initial code. I'm using isolate(), dataTableProxy() and replaceData() which are available since DT version 0.2 to avoid re-rendering the table, which resolves the binding issue and should be faster.
Another problem in your code was that you called session$sendCustomMessage("unbind-DT", "am0") twice instead of using it for "am1".
library(shiny)
library(DT)
library(dplyr)
mtcars$comment <- rep("", nrow(mtcars))
mtcars$row_id <- seq_len(nrow(mtcars))
AppData <- split(mtcars, mtcars[c("cyl", "am")])
# Makes a text input column out of a data frame
make_inputtable <- function(df){
df$comment <-
mapply(
function(comment, id){
as.character(textInput(inputId = sprintf("txt_comment_%s", id),
label = "",
value = comment))
},
comment = df$comment,
id = df$row_id,
SIMPLIFY = TRUE)
df
}
ui <- shinyUI(
fluidPage(
radioButtons(inputId = "rdo_cyl",
label = "Cylinders",
choices = sort(unique(mtcars$cyl)),
inline = TRUE),
h3("Automatic"),
actionButton(inputId = "btn_save_automatic",
label = "Save Comments"),
DT::dataTableOutput("am0"),
hr(),
h3("Manual"),
actionButton(inputId = "btn_save_manual",
label = "Save Comments"),
DT::dataTableOutput("am1"),
# unbind a datatable. Needs to be done before a table is redrawn.
tags$script(HTML(
"Shiny.addCustomMessageHandler('unbind-DT', function(id) {
Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
})"))
)
)
server <- shinyServer(function(input, output, session){
reactiveData <- reactiveValues(
am0_cyl4 = AppData[["4.0"]],
am0_cyl6 = AppData[["6.0"]],
am0_cyl8 = AppData[["8.0"]],
am1_cyl4 = AppData[["4.1"]],
am1_cyl6 = AppData[["6.1"]],
am1_cyl8 = AppData[["8.1"]]
)
# Reactive Objects ------------------------------------------------
ref0 <- reactive({
sprintf("am0_cyl%s", input$rdo_cyl)
})
data0 <- reactive({
reactiveData[[ref0()]]
})
ref1 <- reactive({
sprintf("am1_cyl%s", input$rdo_cyl)
})
data1 <- reactive({
reactiveData[[ref1()]]
})
# Event Observers -------------------------------------------------
observeEvent(
input$btn_save_automatic,
{
in_field <- names(input)[grepl("^txt_comment_", names(input))]
in_field_id <- sub("^txt_comment_", "", in_field)
in_field_id <- as.numeric(in_field_id)
in_field_id <- in_field_id[in_field_id %in% data0()$row_id]
exist_frame <- data0()[c("row_id", "comment")]
new_frame <-
data.frame(
row_id = in_field_id,
comment = vapply(in_field_id,
function(id){ input[[sprintf("txt_comment_%s", id)]]},
character(1)),
stringsAsFactors = FALSE)
Compare <- left_join(exist_frame,
new_frame,
by = "row_id",
suffix = c("_exist", "_new")) %>%
filter(comment_exist != comment_new)
message(sprintf("* %s comment(s) saved", nrow(Compare)))
# Only perform the save operations if there are changes to be made.
if (nrow(Compare)){
session$sendCustomMessage("unbind-DT", "am0")
for(i in seq_len(nrow(Compare))){
row <- Compare$row_id
reactiveData[[ref0()]]$comment[reactiveData[[ref0()]]$row_id == row] <-
input[[sprintf("txt_comment_%s", row)]]
}
print(data0())
}
}
)
# Very similar to btn_save_automatic
observeEvent(
input$btn_save_manual,
{
in_field <- names(input)[grepl("^txt_comment_", names(input))]
in_field_id <- sub("^txt_comment_", "", in_field)
in_field_id <- as.numeric(in_field_id)
in_field_id <- in_field_id[in_field_id %in% data1()$row_id]
exist_frame <- data1()[c("row_id", "comment")]
new_frame <-
data.frame(
row_id = in_field_id,
comment = vapply(in_field_id,
function(id){ input[[sprintf("txt_comment_%s", id)]]},
character(1)),
stringsAsFactors = FALSE)
Compare <- left_join(exist_frame,
new_frame,
by = "row_id",
suffix = c("_exist", "_new")) %>%
filter(comment_exist != comment_new)
message(sprintf("* %s comment(s) saved", nrow(Compare)))
# Only perform the save operations if there are changes to be made.
if (nrow(Compare)){
session$sendCustomMessage("unbind-DT", "am1")
for(i in seq_len(nrow(Compare))){
row <- Compare$row_id
reactiveData[[ref1()]]$comment[reactiveData[[ref1()]]$row_id == row] <-
input[[sprintf("txt_comment_%s", row)]]
}
print(data1())
}
}
)
# Output Objects --------------------------------------------------
output$am0 <-
DT::renderDataTable({
# isolate: render table only once!
make_inputtable(isolate(data0())) %>%
datatable(escape = -13,
options = list(preDrawCallback = DT::JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = DT::JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
}, server = TRUE)
output$am1 <-
DT::renderDataTable({
# isolate: render table only once!
make_inputtable(isolate(data1())) %>%
datatable(escape = -13,
options = list(preDrawCallback = DT::JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = DT::JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
}, server = TRUE)
am0Proxy <- dataTableProxy("am0")
am1Proxy <- dataTableProxy("am1")
observeEvent(data0(), {
replaceData(am0Proxy, make_inputtable(data0()), resetPaging = FALSE) # important
}, ignoreInit = TRUE)
observeEvent(data1(), {
replaceData(am1Proxy, make_inputtable(data1()), resetPaging = FALSE) # important
}, ignoreInit = TRUE)
})
shinyApp(ui = ui, server = server)
You are either unbinding too soon or too late, I am not certain from the code snippet you posted. Can you make multiple objects of the same type to bind to instead?
Edit:
I find this line suspicious:
# unbind a datatable. Needs to be done before a table is redrawn.
tags$script(HTML(
"Shiny.addCustomMessageHandler('unbind-DT', function(id) {
Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
})")) )
Seems like you are unbinding twice and binding only once.
I have the following program. As the title suggests, every time I edit an item on pages after the first page the table goes back to the first page. I'd like the table to stay on the page I'm editing without jumping back to the first page.
I've seen this problem on other threads here but their solutions don't seem to work with current version of DT and shiny packages.
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
DTOutput('x1'),
verbatimTextOutput("print")
),
server = function(input, output, session) {
x = reactiveValues(df = NULL)
observe({
df <- iris
df$Date = Sys.time() + seq_len(nrow(df))
x$df <- df
})
output$x1 = renderDT(x$df, selection = 'none', editable = TRUE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
x$df[i, j] <- isolate(DT::coerceValue(v, x$df[i, j]))
})
output$print <- renderPrint({
x$df
})
}
)
Any help would be appreciated
You can do like this:
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
DTOutput('x1'),
verbatimTextOutput("print")
),
server = function(input, output, session) {
dat <- reactiveVal(cbind(iris, Date = Sys.time() + seq_len(nrow(iris))))
output$x1 = renderDT(isolate(dat()), selection = 'none', editable = TRUE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
dat(editData(dat(), info, proxy, resetPaging = FALSE))
})
output$print <- renderPrint({
dat()
})
}
)
Please see DT-edit. I have copied the 2 relevant examples below:
library(shiny)
library(DT)
dt_output = function(title, id) {
fluidRow(column(
12, h1(paste0('Table ', sub('.*?([0-9]+)$', '\\1', id), ': ', title)),
hr(), DTOutput(id)
))
}
render_dt = function(data, editable = 'cell', server = TRUE, ...) {
renderDT(data, selection = 'none', server = server, editable = editable, ...)
}
shinyApp(
ui = fluidPage(
title = 'Double-click to edit table cells',
dt_output('client-side processing (editable = "cell")', 'x1'),
dt_output('server-side processing (editable = "cell")', 'x5')
),
server = function(input, output, session) {
d1 = iris
d1$Date = Sys.time() + seq_len(nrow(d1))
d5 = d1
options(DT.options = list(pageLength = 5))
# client-side processing
output$x1 = render_dt(d1, 'cell', FALSE)
observe(str(input$x1_cell_edit))
# server-side processing
output$x5 = render_dt(d5, 'cell')
# edit a single cell
proxy5 = dataTableProxy('x5')
observeEvent(input$x5_cell_edit, {
info = input$x5_cell_edit
str(info) # check what info looks like (a data frame of 3 columns)
d5 <<- editData(d5, info)
replaceData(proxy5, d5, resetPaging = FALSE) # important
# the above steps can be merged into a single editData() call; see examples below
})
}
)
I am not sure why you seem to be unnecessarily complicating the process with your reactiveValues but that is probably the cause of your table needing to refresh back to the first page.
When using editable DataTable (package DT) where input is chosen by SelectInput the edits made isn't saved where it should be.
Choosing a specific variable shows for example rows 50-60 from datatable in first rows. Editing them saves the edits on the first rows instead of in rows 50-60.
In example below you can choose variable versicolor and delete setosa. Then edit Petal.Length to random number. Edit should be saved in row 51 but instead it is saved in row 1.
I am thinking about a workaround based on an index-column so the edits are saved in row number (indexrow). But I am not sure how to do that.
#Packages
library(shiny)
library(shinyalert)
library(shinydashboard)
library(leaflet)
library(leaflet.extras)
library(DT)
#data
iris = iris
#Shiny-app (ui)
header = dashboardHeader(title = "SelectInput DataTable example")
sidebar = dashboardSidebar(selectInput("species", "Choose species: ", choices = iris$Species, selected = "setosa", multiple = TRUE))
body = dashboardBody(fluidRow(dataTableOutput("table")))
ui = dashboardPage(skin = "red", header, sidebar, body)
#Server
server <- function(input, output, session) {
output$table = DT::renderDataTable(iris[iris$Species %in% input$species,], editable = TRUE)
proxy = dataTableProxy('table')
observeEvent(input$table_cell_edit, {
info = input$table_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
iris[i, j] <<- DT::coerceValue(v, iris[i, j])
replaceData(proxy, iris, resetPaging = FALSE) # important
})
}
shinyApp(ui = ui, server = server)
Try this:
#Packages
library(shiny)
library(shinydashboard)
library(DT)
#data
iris = iris
#Shiny-app (ui)
header = dashboardHeader(title = "SelectInput DataTable example")
sidebar = dashboardSidebar(selectInput("species", "Choose species: ",
choices = iris$Species, selected = "setosa", multiple = TRUE))
body = dashboardBody(fluidRow(DT::dataTableOutput("table")))
ui = dashboardPage(skin = "red", header, sidebar, body)
# Javascript
js <- function(rows){
c(
"function(settings){",
" var table = settings.oInstance.api();",
sprintf(" var indices = [%s];", paste0(rows-1, collapse = ",")),
" table.rows(indices).remove().draw();",
"}"
)
}
#Server
server <- function(input, output, session) {
dat <- reactiveVal(iris)
Rows <- reactive({
which(iris$Species %in% input$species)
})
output$table = DT::renderDataTable({
rows <- setdiff(1:nrow(iris), Rows())
datatable(
dat(),
editable = TRUE,
options = list(
initComplete = JS(js(rows))
)
)
}, server = FALSE)
observeEvent(input$table_cell_edit, {
info = input$table_cell_edit
info$row = Rows()[info$row+1] - 1
dat(editData(dat(), info))
})
}
shinyApp(ui = ui, server = server)
I am trying to understand how to use the values from an edited data table and do some calculation.
I have a data frame which loads by default. When you click on 'run' it updates the tables based on the input value.
I want the user to edit the values manually in the table and then click on 'run'. Next, I want the app to take the edited values in the data table, run some calculations, and update the table. In this way the user can dynamically see the result their input makes on the table.
library(shiny)
library(DT)
library(dplyr)
#### Module 1 renders the first table
tableMod <- function(input, output, session, modelRun,modelData,budget){
output$x1 <- DT::renderDataTable({
modelRun()
isolate(
datatable(
modelData %>%
mutate(New_Membership = as.numeric(Modified * 0.01)*(budget())),
selection = 'none', editable = TRUE
)
)
})
observeEvent(input$x1_cell_edit, {
df[input$x1_cell_edit$row,input$x1_cell_edit$col] <<- input$x1_cell_edit$value
})
}
tableUI <- function(id) {
ns <- NS(id)
dataTableOutput(ns("x1"))
}
ui <- function(request) {
fluidPage(
tableUI("opfun"),
numericInput("budget_input", "Total Forecast", value = 2),
actionButton("opt_run", "Run")
)
}
server <- function(input, output, session) {
df <- data.frame(Channel = c("A", "B","C"),
Current = c(2000, 3000, 4000),
Modified = c(2500, 3500,3000),
New_Membership = c(450, 650,700),
stringsAsFactors = FALSE)
callModule( tableMod,"opfun",
modelRun = reactive(input$opt_run),
modelData = df,
budget = reactive(input$budget_input))
observeEvent(input$opt_run, {
cat('HJE')
})
}
shinyApp(ui, server, enableBookmarking = "url")
A clean solution (rather than assigning values in other environments) would be to use a reactiveVal in your module which is in sync with the datatable. You can return this reactive from your module to make use of it also in the main application:
library(shiny)
library(DT)
library(dplyr)
#### Module 1 renders the first table
tableMod <- function(input, output, session, modelRun, modelData, budget){
df <- reactiveVal(modelData) ## this variable will be in sync with your datatable
output$x1 <- DT::renderDataTable({
modelRun()
isolate(
datatable(
df() %>% ## ...you always use the synced version here
mutate(New_Membership = as.numeric(Modified * 0.01)*(budget())),
selection = 'none', editable = TRUE
)
)
})
observeEvent(input$x1_cell_edit, {
new_df <- df()
row <- input$x1_cell_edit$row
col <- input$x1_cell_edit$col
value <- as.numeric(input$x1_cell_edit$value)
new_df[row, col] <- value
df(new_df) ## ...and you make sure that 'df' stays in sync
})
list(updated_df = df) ## ...finally you return it to make use of it in the main app too
}
tableUI <- function(id) {
ns <- NS(id)
dataTableOutput(ns("x1"))
}
ui <- function(request) {
fluidPage(
tableUI("opfun"),
numericInput("budget_input", "Total Forecast", value = 2),
actionButton("opt_run", "Run")
)
}
server <- function(input, output, session) {
df <- data.frame(Channel = c("A", "B","C"),
Current = c(2000, 3000, 4000),
Modified = c(2500, 3500,3000),
New_Membership = c(450, 650,700),
stringsAsFactors = FALSE)
result <- callModule( tableMod,"opfun",
modelRun = reactive(input$opt_run),
modelData = df,
budget = reactive(input$budget_input))
observeEvent(input$opt_run, {
str(result$updated_df())
})
}
shinyApp(ui, server, enableBookmarking = "url")
This should work, but not the "cleanest" implementation:
I had to take df out of shiny to make your code work. Used assign to replace df in global environment (not the best idea...) once datatable is edited. But, data is not recalculated untill Run is pressed. Once Run is pressed modelData is overwritten: (modelData <- df). Again not the best idea, probably making modelData reactive will be better idea.
Also, have a look at DT::replaceData. It might be better idea than regenerating full table.
library(shiny)
library(DT)
library(dplyr)
df <- data.frame(Channel = c("A", "B","C"),
Current = c(2000, 3000, 4000),
Modified = c(2500, 3500,3000),
New_Membership = c(450, 650,700),
stringsAsFactors = FALSE)
#### Module 1 renders the first table
tableMod <- function(input, output, session, modelRun,modelData,budget){
observeEvent( input$x1_cell_edit, {
cat ('input$x1_cell_edit \n')
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
df[i, j] <- DT:::coerceValue(v, df[i, j])
assign("df", df, envir = .GlobalEnv)
})
output$x1 <- DT::renderDataTable({
modelRun()
modelData <- df
isolate(
datatable(
modelData %>%
mutate(New_Membership = as.numeric(Modified * 0.01)*(budget())),
selection = 'none', editable = TRUE
)
)
})
}
tableUI <- function(id) {
ns <- NS(id)
dataTableOutput(ns("x1"))
}
ui <- function(request) {
fluidPage(
tableUI("opfun"),
numericInput("budget_input", "Total Forecast", value = 2),
actionButton("opt_run", "Run")
)
}
server <- function(input, output, session) {
callModule( tableMod,"opfun",
modelRun = reactive(input$opt_run),
modelData = df,
budget = reactive(input$budget_input))
observeEvent(input$opt_run, {
cat('HJE')
})
}
shinyApp(ui, server, enableBookmarking = "url")
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.