I am setting up a module, with the purpose of having two data tables in the same app, in different tabs.
I would like to be able to edit and save each table separately.
In my code, only the first "save" button works, and it saves both data tables.
Ideally, each save button should work and save only the corresponding table.
Important: I use the modified version of DTedit:
devtools::install_github('DavidPatShuiFong/DTedit#2.2.1')
Here is my problematic code:
library(shiny)
library(DTedit)
myModuleUI <- function(id,nam) {
ns <- shiny::NS(id)
shiny::tagList(
br(),
##### needs corrections!!
tabsetPanel(tabPanel("XXX", dteditmodUI(ns(nam)),actionButton(ns("reset"), "Reset to Saved", styleclass = "warning"), actionButton(ns("saveBtn"), label = "save"), br(),
id=ns('tabset'), type = 'tabs')
)
)
####
}
myModule <- function(input, output, session,df,nam,taby,wb) {
dfr=reactiveVal()
dfr(df)
Grocery_List_Results <- shiny::callModule(
dteditmod,
id = nam,
thedata =dfr)
# ### save part
savd = data.frame(isolate(dfr()))
observeEvent(input$saveBtn, {
print("Q")
## Add worksheets
st = paste(taby,as.character(unclass(Sys.time())),sep="_")
addWorksheet(wb, st)
writeData(x = Grocery_List_Results$thedata,
wb = wb,
sheet = st)
saveWorkbook(wb, "wb.xlsx", overwrite = T)
savd <<- Grocery_List_Results$thedata
shinyalert(title = "Saved!", type = "success")
})
observeEvent(input$reset, {
dfr(savd)
print(dfr)
shinyalert(title = "Reset to saved data!", type = "info")
})
}
########
ui <- fluidPage(
h3('Grocery List'),
myModuleUI('myModule1',nam="groc"),br(),
myModuleUI('myModule1',nam="groc2")
)
server <- function(input, output, session) {
df= data.frame(
Buy = c('Tea', 'Biscuits', 'Apples',"Tea","Apples"),
Quantity = c(7, 2, 5,9,44),
stringsAsFactors = FALSE
)
file = "AICs.xlsx"
wb <- loadWorkbook(file)
shiny::callModule(myModule, 'myModule1',nam="groc",df=df,taby="Tea",wb)
shiny::callModule(myModule, 'myModule1',nam="groc2",df=df,taby="Apples",wb)
}
shinyApp(ui = ui, server = server)
Appreciate your time!
At long last,
and thanks to this thread,
it seems that I managed to solve this:
library( "openxlsx" )
library("shiny" )
library(shinyalert)
library(shinysky)
library(DTedit) ## used the modified version from https://github.com/DavidPatShuiFong/DTedit
#installed with devtools::install_github('DavidPatShuiFong/DTedit#2.2.1')
results_2_UI <- function(id,nam) {
useShinyalert()
ns <- NS(id)
tabPanel(
title = "Export1",
dteditmodUI(ns(nam)),
actionButton(ns("reset"), "Reset to Saved", styleclass = "warning"), actionButton(ns("saveBtn"), label = "save")
)
}
results_3_UI <- function(id,nam) {
useShinyalert()
ns <- NS(id)
tabPanel(
title = "Export2",
dteditmodUI(ns(nam)),
actionButton(ns("reset"), "Reset to Saved", styleclass = "warning"), actionButton(ns("saveBtn"), label = "save")
)
}
results <- function(input, output, session,df,nam,taby,wb) {
## do some complicated data transformations
dfr=reactiveVal()
dfr(df)
Grocery_List_Results <- shiny::callModule(
dteditmod,
id = nam,
thedata =dfr)
# ### save part
savd = data.frame(isolate(dfr()))
observeEvent(input$saveBtn, {
print("Q")
## Add worksheets
st = paste(taby,as.character(unclass(Sys.time())),sep="_")
addWorksheet(wb, st)
writeData(x = Grocery_List_Results$thedata,
wb = wb,
sheet = st)
saveWorkbook(wb, "wb.xlsx", overwrite = T)
savd <<- Grocery_List_Results$thedata
shinyalert :: shinyalert(title = "Saved!", type = "success")
})
observeEvent(input$reset, {
dfr(savd)
print(dfr)
shinyalert::shinyalert(title = "Reset to saved data!", type = "info")
})
}
### module end
ui <- fluidPage(
tabsetPanel(
id = "tabs",
# results_1_UI(id = "test1"),
results_2_UI(id = "test2",nam="groc"),
results_3_UI(id = "test3",nam="groc2")
)
)
server <- function(input, output, session) {
df= data.frame(
Buy = c('Tea', 'Biscuits', 'Apples',"Tea","Apples"),
Quantity = c(7, 2, 5,9,44),
stringsAsFactors = FALSE
)
file = "AICs.xlsx"
wb <- loadWorkbook(file)
callModule(
module = results,
id = "test2",
nam="groc",df=df,taby="groc",wb=wb
)
callModule(
module = results,
id = "test3",
nam="groc2",df=df,taby="groc2",wb=wb
)
}
shinyApp(ui = ui, server = server)
Fingers crossed, it is OK!
Related
I am trying to achieve following steps while working on the rshiny :
1: creating dynamic tabs on click of the cell : DONE
2: creating dynamic subtabs on click of the parent tab : DONE
3: need to render the datatable based on the following condition :
if ( are matching or == ) then display the data accordingly.
please find the below code for your reference :
library(shiny)
library(DT)
library(shinyWidgets)
shinyApp(
ui <- fluidPage(
headerPanel("Product Details"),
mainPanel(
# Output: Tabset w/ plot, summary, and table ----
tabsetPanel(type = "tabs", id="myTabs",
tabPanel("Company Details", DT::dataTableOutput("data")),
)
)
),
server <- function(input, output, session) {
readXLSXFile <- readxl::read_excel(paste("sample_data.xlsx"),1)
data <- head(readXLSXFile)
tabIndex <- reactiveVal(0)
myValue <- reactiveValues(companyDetails = '')
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
df <- reactiveValues(data = data.frame(
DealID = data[1],
Details = shinyInput(actionButton, length(data)+1,
'button_', label = "Edit",
onclick = 'Shiny.onInputChange(\"select_button\", this.id)',
style = "color: black;
background-color: white",
class="btn-success",
#icon = icon("edit")
),
Tickers = data[3],
stringsAsFactors = FALSE
# row.names = 1:length(data)
))
output$data <- DT::renderDataTable(
df$data, server = FALSE, escape = FALSE, selection = 'none'
)
observeEvent(input$select_button, {
selectedRow <- as.numeric(strsplit(input$select_button, "_")[[1]][2])
myValue$companyDetails <<- paste('click on ',df$data[selectedRow,1])
stringVal <- c(unlist(strsplit(df$data[selectedRow,3],",")))
topTabValue <- c(df$data[selectedRow,1])
subTabData <- c()
datafromsecondRow <- c(data[2][1])
subTabDataOutput <- c()
data_frame_mod <- c()
appendTab("myTabs",
tabPanel(topTabValue,br(),
actionButton("removeTab", "Remove this Tab", icon = icon("remove")),br(),br(),
tabsetPanel(type="tabs", id=c(topTabValue)
),
),
# select=TRUE
)
lapply(1:length(stringVal), function(i) {
subTabData = stringVal[i]
readXLSXFileSheetTwo <- readxl::read_excel(paste("sample_data.xlsx"),2)
dataFileTwo <- head(readXLSXFileSheetTwo)
# print(c(dataFileTwo$Ticker) %in% c(subTabData))
# print("+++++++++++++++++++")
# print(subTabData)
appendTab(c(topTabValue),
tabPanel(subTabData, br(),
tags$h5(paste("You are at -> ",subTabData)),
output$subTabData <- DT::renderDataTable({
dataFileTwo[c(dataFileTwo$Ticker) %in% c(subTabData),TRUE]
datatable(dataFileTwo, options = list(dom = 'ft'),escape=FALSE)
})
),
# print(c(subTabData))
)
observeEvent(input$subTabData, {
appendTab(subTabData,
tabPanel(topTabValue,br(),
actionButton("removeTab", "Remove this Tab", icon = icon("remove")),br(),br(),
tabsetPanel(type="tabs", id=c(topTabValue)
),
),
)
})
})
})
observeEvent(input$removeTab, {
removeTab("myTabs", target=input$myTabs)
})
output$myText <- renderText({
myValue$companyDetails
})
}
)
Please help me to solve this point.
output$subTabData <- DT::renderDataTable({
**dataFileTwo[c(dataFileTwo$Ticker) %in% c(subTabData),TRUE]**
datatable(dataFileTwo, options = list(dom = 'ft'),escape=FALSE)
})
It is still rendering the whole dataset.. I stuck on conditional render the data on click of subtab.
I use Timevis package.
first of all I read an excel file with missions.
In my code the user can see all the missions on a time line, and he can edit/add/remove any missions.
after the user make a change I can see the update table below.
I want to save to my excel file every update that the user make.
this is my code:
library(shiny)
library(timevis)
library(readxl)
my_df <- read_excel("x.xlsx")
data <- data.frame(
id = my_df$id,
start = my_df$start,
end = my_df$end,
content = my_df$content
)
ui <- fluidPage(
timevisOutput("appts"),
tableOutput("table")
)
server <- function(input, output) {
output$appts <- renderTimevis(
timevis(
data,
options = list(editable = TRUE, multiselect = TRUE, align = "center")
)
)
output$table <- renderTable(
input$appts_data
)
}
shinyApp(ui, server)
You can use actionButton/ observe to call saveworkbook (package openxlsx) to save your changes. Technically you are not saving these changes, but replacing the file with an identical file containing the changes.
library(shiny)
library(openxlsx)
library(timevis)
library(readxl)
my_df <- read_excel("x.xlsx")
data <- data.frame(
id = my_df$id,
start = my_df$start,
end = my_df$end,
content = my_df$content
)
mypath = paste0(getwd(), "/x.xlsx") # Path to x.xlsx
ui <- fluidPage(
timevisOutput("appts"),
tableOutput("table"),
actionButton("save", "Save")
)
server <- function(input, output) {
output$appts <- renderTimevis(
timevis(
data,
options = list(editable = TRUE, multiselect = TRUE, align = "center")
))
observeEvent(input$save,
{
my_df<- createWorkbook()
addWorksheet(
my_df,
sheetName = "data"
)
writeData(
wb = my_df,
sheet = "data",
x = input$appts_data,
startRow = 1,
startCol = 1
)
saveWorkbook(my_df, file = mypath,
overwrite = TRUE)
})
output$table <- renderTable(
input$appts_data
)
}
shinyApp(ui, server)
I'm modularizing a Shiny app I developed using shinydashboard packages. Despite it traditionally works when I use it without involving modules, I can't make it work when I try to divide it into modules and submodules. Here I would like to combine two UIs (one for the sidebar, one for the body) in order to upload a dataset from the sidebar and show it into the body.
I'd be very glad if anybody could provide me some help with this.
Here is the code of the general Shiny app:
library(shiny)
library(excelR)
library(vroom)
library(readxl)
library(janitor)
library(dplyr)
library(shinydashboard)
library(shinydashboardPlus)
# # load separate module and function scripts
source("modules.R")
# app_ui
app_ui <- function() {
tagList(
shinydashboardPlus::dashboardPagePlus(
header = shinydashboardPlus::dashboardHeaderPlus(title = "module_test",
enable_rightsidebar = FALSE),
sidebar = shinydashboard::dashboardSidebar(
shinydashboard::sidebarMenu(id = "tabs",
import_sidebar_ui("import"))
),
body = shinydashboard::dashboardBody(shinydashboard::tabItems(
import_body_ui("import"))
),
rightsidebar = NULL,
title = "Module App"
)
)
}
# app_server
app_server <- function(input, output, session) {
shiny::moduleServer(id = "import", module = import_server)
}
####################################################################
run_app <- function(...) {
shiny::shinyApp(
ui = app_ui,
server = app_server)
}
#---------------------------------
run_app()
and here is the modules.R file I wrote containing the UIs for sidebar and body, plus the server:
# Import module ####
#
# Import sidebar UI
import_sidebar_ui <- function(id) {
ns <- NS(id)
shinydashboard::menuItem("Module Testing",
tabName = "tab_testing_mod",
icon = icon("th"),
tagList(
selectInput(ns("input_type"),
"Type of file:",
choices = c("Choose one" = "",".csv" = "csv",
".txt" = "txt", ".xls/.xlsx" = "xlsx"),
selected = NULL),
uiOutput(ns("inputControls")),
fileInput(ns("file"), "Data", buttonLabel = "Upload..."),
checkboxInput(ns("rownames"), "Check if 1st column contains rownames"),
checkboxInput(ns("constant"), "Remove constant columns?"),
checkboxInput(ns("empty"), "Remove empty cols?"),
actionButton(ns("bttn_import"), "Import data")
)
)
}
# Import body UI
import_body_ui <- function(id) {
ns <- NS(id)
shinydashboard::tabItem(tabName = "tab_testing_mod",
fluidRow(
h3("Imported Data"),
excelR::excelOutput(ns("preview")))
)
}
# Import server
import_server <- function(input, output, session) {
ns <- session$ns
output$inputControls <- renderUI({
tagList(
switch(input$input_type,
"csv" = textInput("delim", "Delimiter (leave blank to guess)", ""),
"txt" = textInput("delim", "Delimiter (leave blank to guess)", "")
),
switch(input$input_type,
"xlsx" = numericInput("sheet", "Sheet number", value = 1))
)
})
raw <- reactive({
req(input$file)
if (input$input_type == "csv" || input$input_type == "txt") {
delim <- if (input$delim == "") NULL else input$delim
data <- vroom::vroom(input$file$datapath, delim = delim)
} else if (input$input_type == "xlsx") {
data <- tibble::as.tibble(readxl::read_excel(input$file$datapath, sheet = input$sheet, col_names = TRUE))
} else {
return(NULL)
}
raw <- data
raw
})
tidied <- eventReactive(input$bttn_import,{
out <- raw()
if (input$empty) {
out <- janitor::remove_empty(out, "cols")
}
if (input$constant) {
out <- janitor::remove_constant(out)
}
if (input$rownames) {
out <- tibble::column_to_rownames(out, var = colnames(out[1]))
}
out <- out %>% dplyr::mutate_if(is.character,as.factor)
out
})
output$preview <- excelR::renderExcel({
excelR::excelTable(data = raw(),
colHeaders = toupper(colnames(raw())),
fullscreen = FALSE,
columnDrag = TRUE,
rowDrag = TRUE,
wordWrap = FALSE,
search =TRUE,
showToolbar = TRUE,
minDimensions = c(ncol(raw()),10)
)
})
}
It seems to me I can upload the dataset (.csv, .txt or .xlsx) files but I can't show it into the body.
I'd be very glad if you can help me, thank you very much in advance for your assistance.
What I am attempting to do, is to allow the user to pass in a configuration/lookup excel table into shiny, display this table in shiny, allow the user to make cells edits in shiny, and use the values that were edited from the editable table for calculations. My problem arises for the last step "use the values that were edited from the editable table for calculations".
The excel file consists of 2 tabs with data of the following content:
Tab1 Name: "parameters"
data.frame(Name = c("a", "b", "c"), Value = c(1:3))
Tab2 Name: "parameters2"
data.frame(Name = c("a", "b", "c"), Value = c(4:6))
The ideal shiny app would do the following:
1) At upload, perform a calculation adding the unchanged first values of Tab 1 and Tab 2. This would be 1 + 4 = 5.
2) If user edits Tab 1's value of 1 to 8, then the calculation would result in 8 + 4 = 12.
Effectively, I want to use the edited tables values to update all my calculations if the user makes any edits to it. I know this can be done by simply uploading a new file in shiny, but I would rather allow them to do this in shiny as opposed to uploading a new file.
Here is my shiny app. Appreciate any help/guidance!
library(shiny)
library(DT)
shinyApp(
ui <- fluidPage(
fileInput(inputId = "config", label = "Upload Configuration File",
multiple = F, accept = c(".xlsx", ".xls")),
verbatimTextOutput("txt"),
tagList(tags$head(tags$style(type = 'text/css','.navbar-brand{display:none;}')),
navbarPage(title = "",
tabPanel(title = "Parameters",
dataTableOutput(outputId = "edit.param", width = 2)),
tabPanel(title = "Parameters2",
dataTableOutput(outputId = "edit.param2", width = 2))
)
)
),
server = function(input, output, session) {
config.path = reactive({
inFile = input$config
if(is.null(inFile)) {
return(NULL)
} else {
return(inFile$datapath)
}
})
df.param = reactive({
read_excel(path = config.path(), sheet = "parameters")
})
df.param2 = reactive({
read_excel(path = config.path(), sheet = "parameters2")
})
output$edit.param = renderDT(df.param(), selection = "none", server = F, editable = "cell")
output$edit.param2 = renderDT(df.param2(), selection = "none", server = F, editable = "cell")
observeEvent(input$edit.param_cell_edit, {
df.param()[input$edit.param_cell_edit$row, input$edit.param_cell_edit$col] <<- input$edit.param_cell_edit$value
})
observeEvent(input$edit.param2_cell_edit, {
df.param()[input$edit.param2_cell_edit$row, input$edit.param2_cell_edit$col] <<- input$edit.param2_cell_edit$value
})
output$txt = reactive({
df.param()$value[1] + df.param2()$value[1]
})
}
)
I also tried this for the server section and had no luck either:
output$edit.param = renderDT(df.param(), selection = "none", server = F, editable = "cell")
output$edit.param2 = renderDT(df.param2(), selection = "none", server = F, editable = "cell")
observe(input$edit.param_cell_edit)
observe(input$edit.param2_cell_edit)
Could you try this? (I have not tried).
library(shiny)
library(DT)
shinyApp(
ui <- fluidPage(
fileInput(inputId = "config", label = "Upload Configuration File",
multiple = F, accept = c(".xlsx", ".xls")),
verbatimTextOutput("txt"),
tagList(tags$head(tags$style(type = 'text/css','.navbar-brand{display:none;}')),
navbarPage(title = "",
tabPanel(title = "Parameters",
dataTableOutput(outputId = "edit_param", width = 2)),
tabPanel(title = "Parameters2",
dataTableOutput(outputId = "edit_param2", width = 2))
)
)
),
server = function(input, output, session) {
config.path = reactive({
inFile = input$config
if(is.null(inFile)) {
return(NULL)
} else {
return(inFile$datapath)
}
})
df_param <- reactiveVal()
observe({
req(config.path())
df_param(read_excel(path = config.path(), sheet = "parameters"))
})
df_param2 <- reactiveVal()
observe({
req(config.path())
df_param2(read_excel(path = config.path(), sheet = "parameters2"))
})
output$edit_param = renderDT({
req(df_param())
datatable(isolate(df_param()), selection = "none", editable = "cell")
})
output$edit_param2 = renderDT({
req(df_param2())
datatable(isolate(df_param2()), selection = "none", editable = "cell")
})
proxy <- dataTableProxy("edit_param")
proxy2 <- dataTableProxy("edit_param2")
observeEvent(input$edit_param_cell_edit, {
info <- input$edit_param_cell_edit
df_param(editData(df_param(), info, proxy, resetPaging = FALSE))
})
observeEvent(input$edit_param2_cell_edit, {
info <- input$edit_param2_cell_edit
df_param2(editData(df_param2(), info, proxy2, resetPaging = FALSE))
})
output$txt = renderPrint({
df_param()$value[1] + df_param2()$value[1]
})
}
)
How do I call a shiny module from within a shiny module with passing selections from the first module?
As an example I wrote a app to show the Star Wars subjects from dplyr in a DT::data table (module StarWars). The related films from the same data set should be shown in another DT::data table in another sub tab (module Films).
I pass the table selected subject in a reactive value sw_rows_selected_rct from module StarWars to module Films but browser() statement in module Films is not passed.
# Test call of modules inside modules
library(tidyverse)
#' Shiny StarWars module
#'
ui_Films <-
function(id,
title = id,
...,
value = title,
icon = NULL) {
ns <- shiny::NS(id)
tab <- tabPanel(title,
h4("StarWars Films"),
DT::dataTableOutput(outputId = ns("Films")))
}
ui_StarWars <-
function(id,
title = id,
...,
value = title,
icon = NULL) {
ns <- shiny::NS(id)
tab <- tabPanel(title,
DT::dataTableOutput(outputId = ns("StarWars")),
tabsetPanel(ui_Films(
id = ns("Films"), title = "...by Films"
)))
}
ui <- shinyUI(navbarPage(
"Call Modules in Modules test",
ui_StarWars("StarWars", title = "StarWars")
))
Films <-
function(input,
output,
session,
sw_data,
sw_selection) {
ns <- session$ns
sw_films_rct <- observe({
req(sw_data, is.data.frame(sw_selection))
browser() # not reached!!!
sw_films_rct <-
sw_data %>% {
if (is_null(sw_selection))
.
else
filter(., name == sw_selection$name)
}
})
output$StarWarsFilms <- DT::renderDataTable({
req(is.data.frame(sw_films_rct))
DT::datatable(sw_films_rct,
selection = 'single',
options = list(pageLength = 5))
})
}
StarWars <-
function(input, output, session, sw_data) {
sw_rows_selected_rct = reactiveVal()
ns <- session$ns
sw_rows_selected_rct = observeEvent(input$StarWars_rows_selected, {
req(sw_data, input$StarWars_rows_selected != 0)
browser()
sw_data[input$StarWars_rows_selected, ]
})
md_films <- callModule(
module = Films,
id = "Films",
sw_data = sw_data,
sw_selection = sw_rows_selected_rct
)
output$StarWars <- DT::renderDataTable({
req(is.data.frame(sw_data))
DT::datatable(sw_data,
selection = 'single',
options = list(pageLength = 5))
})
}
server <- shinyServer(function(input, output, session) {
sw_data_rct = reactive({
dplyr::starwars %>% mutate(films = NULL,
vehicles = NULL,
starships = NULL)
})
md_StarWars = callModule(module = StarWars,
id = "StarWars",
sw_data = sw_data_rct())
})
# Run the application
shinyApp(ui = ui, server = server)
Your code had a few errors. Remember, observe and observeEvents don't have return values. Set the value of your reactives through the nameofReactive(newValue). Your initial goal is possible if you give the reactive to the module, not the current value of the reactive, so that it can change throughout the course of using the app. In the module, you then have to you the value of the reactive, by using () on the reactive. Oh, and your last output had the wrong name (output$Films should be correct). Here is the working app:
library(tidyverse)
#' Shiny StarWars module
#'
ui_Films <-
function(id, title = id, ..., value = title, icon = NULL) {
ns <- shiny::NS(id)
tab <- tabPanel(title,
h4("StarWars Films"),
DT::dataTableOutput(outputId = ns("Films"))
)
}
ui_StarWars <-
function(id, title = id, ..., value = title, icon = NULL) {
ns <- shiny::NS(id)
tab <- tabPanel(title,
DT::dataTableOutput(outputId = ns("StarWars")),
tabsetPanel(
ui_Films(id = ns("Films"), title = "...by Films"))
)
}
ui <- shinyUI(
navbarPage(
"Call Modules in Modules test",
ui_StarWars("StarWars", title = "StarWars")
)
)
Films <-
function(input, output, session, sw_data, sw_selection) {
ns <- session$ns
sw_films_rct <- reactiveVal()
observe({
sw_films_rct(sw_data() %>% {if(is_null(sw_selection())) . else filter(., name == sw_selection()$name)})
})
output$Films <- DT::renderDataTable({
req(is.data.frame(sw_films_rct()))
DT::datatable(sw_films_rct(),
selection = 'single',
options = list(pageLength = 5))
})
}
StarWars <-
function(input, output, session, sw_data) {
sw_rows_selected_rct= reactiveVal()
ns <- session$ns
observeEvent(input$StarWars_rows_selected, {
req(sw_data(), input$StarWars_rows_selected != 0)
sw_rows_selected_rct(sw_data()[input$StarWars_rows_selected,])
})
md_films <- callModule(module = Films, id = "Films",
sw_data= sw_data,
sw_selection= sw_rows_selected_rct)
output$StarWars <- DT::renderDataTable({
req(is.data.frame(sw_data()))
DT::datatable(sw_data(),
selection = 'single',
options = list(pageLength = 5))
})
}
server <- shinyServer(function(input, output, session) {
sw_data_rct= reactive({dplyr::starwars %>% mutate(films = NULL, vehicles = NULL, starships = NULL)})
md_StarWars= callModule(module = StarWars, id = "StarWars", sw_data = sw_data_rct)
})
# Run the application
shinyApp(ui = ui, server = server)