I'm having trouble using the "shinyFilesButton()" and "shinyFilesChoose()" functionality within modules in R shiny.
I believe my issue is related to the namespace functions ("ns()") that effectively create new, unique ids within the modules.
Where do I put the ns() call within the shinyFiles functions? How do I handle this issue on the server side?
I've mocked up an example, with code shown below. The app just selects a file and tells you the info on what you selected. Note that currently no ns() calls are used with any shinyFiles functions. (I've tried wrapping the shinyFilesButton()'s id in the ns(), but then it doesn't match with the shinyFileChoose.)
Currently, this app below will show me files, but only in the root directory. I can't delve deeper into other directories. Additionally, the select button will highlight, but nothing will happen when used.
Edit: I've update the code with an attempt at using the namespaces, per suggestions in the comments. I'm using the ns() function in the shinyFilesButton() call (ui side) and nothing in the server side.
Now I can't see any files when using the file chooser.
Any help?
Below is my code for the app.r file:
#App.R
#Demonstrate issues with ShinyFiles and namesspaces in modules
library(shiny)
library(shinyFiles)
source("shinyFiles_module.R")
server <- function(input, output, session) {
#module Way
callModule(sample,
id="testid",
root_dirs=c(root_on_mac="/Users/Ryan/Desktop/"))
}
ui <- fluidPage(
tagList(
h2("Module Way"),
sample_UI(id = "testid",
label = "shiny file test")
)
)
shinyApp(ui = ui, server = server)
And for the module:
#Sample shinyFiles Module
#trying to get File path using ShinyFiles within a Module
library(shiny)
library(shinyFiles)
#Settings UI function:
# Module UI function
sample_UI <- function(id, label = "Shiny file test") {
# Create a namespace function using the provided id
ns <- NS(id)
#begin UI (wrap all input/ouput in ns() call)
tagList(
strong("Selected Location: "), verbatimTextOutput(ns("file_path")),
shinyFilesButton(
id=ns("get_file_path"),
label="Click Here to Select",
title="Select a file",
multiple= FALSE,
buttonType = "default",
class = NULL)
)
}
# Module server function
sample <- function(input,
output,
session,
root_dirs,
id_value) {
shinyFileChoose(input, id="get_file_path", roots=root_dirs, session=session)
output$file_path <- renderPrint({
parseFilePaths(roots=root_dirs, input$get_file_path)
})
}
Change your module to this and your program works:
library(shiny)
library(shinyFiles)
#Settings UI function:
# Module UI function
sample_UI <- function(id, label = "Shiny file test") {
# Create a namespace function using the provided id
ns <- NS(id)
#begin UI (wrap all input/ouput in ns() call)
tagList(
strong("Selected Location: "), verbatimTextOutput(ns("file_path")),
shinyFilesButton(
id=ns("get_file_path"),
label="Click Here to Select",
title="Select a file",
multiple= FALSE,
buttonType = "default",
class = NULL)
)
}
# Module server function
sample <- function(input,
output,
session,
root_dirs) {
ns <- session$ns
shinyFileChoose(input, id=ns("get_file_path"), roots=root_dirs, session=session)
output$file_path <- renderPrint({
parseFilePaths(roots=root_dirs, input$get_file_path)
})
}
I have the same issue and I resolve it by following Carl's suggestion:
use ns() in ui
remove ns() in server
I am using R3.6.1 and it works. In R3.4 it has problem navigate into the subfolders.
library(shiny)
library(shinyFiles)
#Settings UI function:
# Module UI function
sample_UI <- function(id, label = "Shiny file test") {
# Create a namespace function using the provided id
ns <- NS(id)
#begin UI (wrap all input/ouput in ns() call)
tagList(
strong("Selected Location: "), verbatimTextOutput(ns("file_path")),
shinyFilesButton(
id=ns("get_file_path"),
label="Click Here to Select",
title="Select a file",
multiple= FALSE,
buttonType = "default",
class = NULL)
)
}
# Module server function
sample <- function(input,
output,
session,
root_dirs,
id_value) {
shinyFileChoose(input, id="get_file_path", roots=root_dirs, session=session)
output$file_path <- renderPrint({
parseFilePaths(roots=root_dirs, input$get_file_path)
})
}
Related
In my shiny server I am figuring out the name of a markdown file which I want to show in the UI. I know how to pass the file name, as a string, back to the UI but I don't now how to tell includeMarkdown() to treat the string as a file name.
My code so far is below. Any advice?
library(shiny)
fileConn<-file("hello.md")
writeLines(c("# Hello","# World"), fileConn)
close(fileConn)
ui <- fluidPage(
includeMarkdown("hello.md"),
br(),
div("File name text:", textOutput("fileNameText", inline = TRUE)),
#includeMarkdown(fileNameText) # this needs help
)
server <- function(input, output, session) {
selectedName <- reactive({
paste0("hello.md") # this is actually more complicated...
})
output$fileNameText <- renderText(
paste0(selectedName())
)
}
shinyApp(ui = ui, server = server)
Your example code works fine, but from your description, I am thinking your asking how to pass a different filename to includeMarkdown() that was created somewhere else in the app, correct?
The first step is to understand includeMarkdown() as a UI element that will change depending on other UI elements (and stuff that happens in server). The solution is to use a placeholder in the ui to hold the place for the includeMarkdown() element, and create that particular element in server using renderUI.
Hopefully you can follow this example. I'm using uiOutput('displayFile') to hold the place for the element that's created in server.
library(shiny)
fileConn<-file("hello.md")
writeLines(c("# Hello","# World"), fileConn)
close(fileConn)
fileConn1<-file("goodbye.md")
writeLines(c("# Goodbye","# Everyone!"), fileConn1)
close(fileConn1)
ui <- fluidPage(
selectInput('file_selection', 'Choose Markdown File:', choices=c('hello.md','goodbye.md')),
uiOutput('displayFile')
)
server <- function(input, output, session) {
output$displayFile <- renderUI({
includeMarkdown(input$file_selection)
})
}
shinyApp(ui = ui, server = server)
My app has several screens and I deal with it using tabsetPanel(), hiding the tab headers (I
leave them visible here for debugging) and selecting them using updateTabsetPanel()
It starts on a home screen (coded into mod_home_ui() / mod_home_server())
You push a button to trigger an action, there would be several but I just left one here, called "learn" (coded into mod_learn_ui() / mod_learn_server())
The "learn" module itself contains games, here I left only two games and used the same module functions for both for simplicity.
A reactive value panel_flag, determines which game should be played, here I force it to FALSE, which means game2 should be played.
This last step doesn't work as I expect, while messages show that the code went through the right updateTabsetPanel() call, the expected tab isn't selected, and moreover, the expected text isn't shown on top of the screen.
This looks like a namespacing issue but I don't understand what I did wrong here.
The code below can be copy pasted in one go to run the app and here's a gif of what would happen :
# main ui and server
app_ui <- function() {
tagList(
fluidPage(
title = "My app",
tabsetPanel(
id = "switcher",
#type = "hidden",
selected = "home",
tabPanel("home", mod_home_ui("home_ui")),
tabPanel("learn", mod_learn_ui("learn_ui"))
)
)
)
}
app_server <- function(input, output,session) {
learn <- callModule(mod_home_server, "home_ui")
observeEvent(learn(), {
message("In app_server: observeEvent on learn() to switch to 'learn' panel")
updateTabsetPanel(session, "switcher", selected = "learn")
})
callModule(mod_learn_server, "home_ui", learn = learn)
}
# home module
mod_home_ui <- function(id){
ns <- NS(id)
tagList(
textOutput(ns("some_text")),
actionButton(ns("learn"), "learn")
)
}
mod_home_server <- function(input, output, session){
output$some_text <- renderText("I expect clicking on the above to trigger game2, not game1")
ns <- session$ns
reactive({
res <- req(input$learn)
message(
'In mod_home_server: returning req(input$learn) in mod_home_server to trigger learn()')
res
})
}
# learn module
mod_learn_ui <- function(id){
ns <- NS(id)
tabsetPanel(
id = ns("switcher"),
#type = "hidden",
tabPanel("game1", mod_game_ui(ns("game1_ui"))),
tabPanel("game2", mod_game_ui(ns("game2_ui")))
)
}
mod_learn_server <- function(input, output, session, learn){
ns <- session$ns
panel_flag <- eventReactive(learn(), {
message('In mod_learn_server: eventReactive on learn() to trigger relevant game')
# in reality this would be computed or random
FALSE
})
observeEvent(panel_flag(), {
message('In mod_learn_server: observeEvent on panel_flag()')
if (panel_flag()) {
message('In mod_learn_server: select "game1" panel')
updateTabsetPanel(session, "switcher", selected = "game1")
} else {
message('In mod_learn_server: select "game2" panel')
updateTabsetPanel(session, "switcher", selected = "game2")
}
})
callModule(mod_game_server, "game1_ui")
callModule(mod_game_server, "game2_ui")
}
# game module
mod_game_ui <- function(id){
ns <- NS(id)
tagList(
textOutput(ns("some_text")),
"I expect another line of text above this one"
)
}
mod_game_server <- function(input, output, session){
ns <- session$ns
output$some_text <- renderText("I expect this to be shown")
}
library(shiny)
shinyApp(app_ui, app_server)
callModule(mod_learn_server, "learn_ui", learn = learn)
instead of
callModule(mod_learn_server, "home_ui", learn = learn)
should fix it.
To make sure this doesn't happen again I made a package that tests the consistency of the shiny code, it is designed with the {golem} framework and conventions in mind.
Install with remotes::install_github("moodymudskipper/shinycheck")
This is what I get when I run shinycheck::check_shiny() on my real app (which is slightly different from the above):
shinycheck::check_shiny()
-----------------------------------------------------------------------
Check that all module scripts contain exactly 2 functions named appropriately
-----------------------------------------------------------------------
Check that all module ui functions use ns() or NS() on argument named id/inputId/outputId
-----------------------------------------------------------------------
Check that in ui, module ui functions, named `mod_MODULE_ui` refer to modules which exist and ids fed to them are prefixed with "MODULE_"
-----------------------------------------------------------------------
Check that ns() and NS() are never called in an argument that isn't id/inputId/outputId
-----------------------------------------------------------------------
Check that the module args of callModule are of the form "mod_MODULENAME_server", that there is an R file properly named for "MODULENAME", and that the id argument is prefixed by "MODULENAME_"
* In 'mod_main_server', a call to `callModule` has a module argument `mod_learn_server` and an `id` argument 'home_ui' that is not prefixed by the module name 'learn'
-----------------------------------------------------------------------
Check that modules and module ids mentionned on both ui and server side are consistent
* In 'mod_main_ui' we find the module id 'learn_ui' but we don't find it in 'mod_main_server'
We find :
In 'mod_main_server', a call to callModule has a module argument mod_learn_server and an id argument 'home_ui' that is not prefixed by the module name 'learn'
In 'mod_main_ui' we find the module id 'learn_ui' but we don't find it in 'mod_main_server'
This would have made debugging trivial.
See more at https://github.com/moodymudskipper/shinycheck
In shinydashboard there is the amazing convenience of putting widgets into the menuItem(menuSubItems()) portion of the dashboardSidebar(). But I want the several elements of my UI and Server coded into modules so I can adhere to the golem framework... and I'm not seeing a clear way to do this without creating multiple UI functions for a single module. I've seen the shinydashboard golem example on github and it's too simple of an example that doesn't help.
For example, Is there a way I can do this?
In a module format:
library(shiny)
library(shinydashboard)
### The Sidebar Menu with a Widget Subitem
mod_myAppSidebar_ui<-function(id) {
ns <- NS(id)
tagList(menuItem("Attributes", tabName="ourdata",
textInput("textSearch","SQL Search String", value = "")))
}
### The Dashboard Body output
mod_myAppBody_ui<-function(id) {
ns <- NS(id)
tagList(box(shiny::dataTableOutput(outputId = "OutputData")))
}
mod_myApp_server<-function(input, output, session) {
ns <- session$ns
output$OutputData<-shiny::renderDataTable({
somedata=data.frame(Rows=letters,Indexes=1:length(letters))
somedata[grepl(tolower(input$textSearch),somedata$Rows),]
})
}
### DashboardPage requires separate arguments for the UI elements
ui <- dashboardPage(header = dashboardHeader(title = "Rosetta"),
sidebar = dashboardSidebar(mod_myAppSidebar_ui("MySearch")),
body = dashboardBody(mod_myAppBody_ui("MySearch")))
server <- function(input, output, session) {
callModule(mod_myApp_server, "MySearch")
}
shinyApp(ui,server)
Is there any way to make this kind of thing work? The widget isn't showing up, likely because I don't think the modular framework allows for me to make two different UI elements for one piece of functionality.
Alright, so I got this working... surprisingly didn't take too much. I don't know if the complexity of my app will break this, but for anyone who was hoping to do this, maybe this is helpful:
library(shiny)
library(shinydashboard)
library(DT)
mod_myAppSidebar_ui<-function(id) {
ns <- NS(id)
tagList(menuItem("Attributes", tabName="ourdata",
textInput(ns("textSearch"),"SQL Search String", value = ""),
actionButton(ns("go"),label = "Search")))
}
mod_myAppBody_ui<-function(id) {
ns <- NS(id)
tagList(fluidRow(title = "Data Selected",
box(DT::dataTableOutput(outputId = ns("OutputData")))))
}
mod_myApp_server<-function(input, output, session, r) {
ns <- session$ns
observeEvent( input$go , {
r$textSearch<-input$textSearch
print(r$textSearch)
somedata=data.frame(Rows=letters,Indexes=1:length(letters))
r$chooseData<-somedata[grepl(tolower(input$textSearch),somedata$Rows),]
})
output$OutputData<-DT::renderDataTable(r$chooseData)
}
ui <- dashboardPage(header = dashboardHeader(title = "Rosetta"),
sidebar = dashboardSidebar(mod_myAppSidebar_ui("MySearch")),
body = dashboardBody(mod_myAppBody_ui("MySearch")))
server <- function(input, output, session) {
r<-reactiveValues()
callModule(mod_myApp_server, "MySearch", r)
}
shinyApp(ui,server)
I am making an app with drag and drop feature using the ShinyDND package. I would like to pass a list from input as a parameter of dragSetUI, a function that needs to be run in ui.R. I tried renderUI and uiOutput, and it almost works, but the dragged elements fail to be dropped in the drop area. As you can see in the example below, nonreactive choices work like charm. I tried creating a reactive object as well as text output, but I could not find documentation on how to refer to these objects in ui.R. Normally one would refer to output$x as "x" in Output, but here, if I add anything between quotes, it reads as string. I am really stuck with this.
library(shiny)
library(shinyDND)
nonreactive_choices<-as.list(c("a","b","c"))
ui <- shinyUI(
mainPanel(
textInput("choices","Put here a,b,c:"),
dragSetUI("drag", textval = "reactive_choices"),
dragSetUI("drag", textval = "choice_rv"),
textOutput("reactive_choices"),
dragSetUI("drag", textval = nonreactive_choices),
dropUI("drop")
)
)
server = shinyServer(function(input, output,session) {
output$reactive_choices<-reactive({
strsplit(input$choices,",")
})
observe({
chlist<-strsplit(input$choices,",")
choice_rv<-reactiveVal(chlist)
})
})
# Run the application
shinyApp(ui = ui, server = server)
Let's see why the renderUI approach does not work with shinyDND. An app using shinyDND is linked to the dragndrop.js file, which is in the shinyDND folder. In this file one can see:
$(document).ready(function(){
......
$(".dragelement").on("dragstart",function(e){
e.originalEvent.dataTransfer.setData("Text",e.target.id);
});
......
});
This defines the action to perform on elements having class dragelement when a drag is starting, and this is defined when the document is ready. dragSetUI creates such elements.
When you use a dragSetUI inside a renderUI, this creates new elements of class dragelement. But the action defined on such elements in $(document).ready is not effective for these new elements, because this action has been defined just after the document is ready, and then before the effect of renderUI.
A solution consists in defining the action of the event dragstart inside the renderUI. This works:
library(shiny)
library(shinyDND)
nonreactive_choices<-as.list(c("a","b","c"))
ui <- shinyUI(
mainPanel(
textInput("choices","Put here d,e,f:"),
dragSetUI("drag", textval = nonreactive_choices),
uiOutput("dragset"),
dropUI("drop")
)
)
server = shinyServer(function(input, output,session) {
reactive_choices <- reactive({
strsplit(input$choices,",")[[1]]
})
output$dragset <- renderUI({
tagList(
dragSetUI("drag2", textval = as.list(reactive_choices())),
tags$script('$(".dragelement").on("dragstart",function(e){
e.originalEvent.dataTransfer.setData("Text",e.target.id);
});'
)
)
})
})
# Run the application
shinyApp(ui = ui, server = server)
Comment by #ismirsehregal helped me find the solution: shinyjqui can be used for my purposes and it seems to work from inside renderUI. Here is the edited code that does exactly what I needed.
library(shiny)
library(shinyjqui)
ui <- fluidPage(
textInput("choices","Put here a,b,c:"),
uiOutput("reactiveselect"),
orderInput(inputId = 'drop', label = 'Reactive drop', items = NULL,placeholder = "drop here..."),
verbatimTextOutput("droppedorder")
)
server <- function(input, output) {
output$reactiveselect <- renderUI({
req(input$choices)
reactiveitems<- unlist(strsplit(input$choices,","))
orderInput("groupstochoose", "groups to choose from:", connect='drop',items=reactiveitems)
})
output$droppedorder<-
renderPrint({input$drop_order})
}
shinyApp(ui, server)
I need to deal with a huge file (>500mb) in R. So instead of loading such heavy file in R environment, I process the file in chunks of specific number of rows and finally get the aggregate values.
I need user to specify the file (using some kind of browse functionality) so that I can feed the file path to my algorithm
fileConnection <-file( "../output/name.txt", open="w")
Is there any way to get only file path from Shiny UI based on the address specified by user? I tried ShinyFiles package, but it gives only directory to choose, not file.
This functionality is available in the shinyFiles package. Have a look at this minimal example:
library(shiny)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton("Btn_GetFile", "Choose a file" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default", class = NULL),
textOutput("txt_file")
)
server <- function(input,output,session){
volumes = getVolumes()
observe({
shinyFileChoose(input, "Btn_GetFile", roots = volumes, session = session)
if(!is.null(input$Btn_GetFile)){
# browser()
file_selected<-parseFilePaths(volumes, input$Btn_GetFile)
output$txt_file <- renderText(as.character(file_selected$datapath))
}
})
}
shinyApp(ui = ui, server = server)
EDIT: This approach worked when running on RStudio desktop on Windows. But it fails on RStudio Server on Unix, as the file.choose dialog appears in the RStudio window instead of in the app.
Rather than uploading the file, we can instead just send a text string that is the file path to the app.
Consider the following minimal example:
library(shiny)
ui <- fluidPage(
actionButton("get", "Send file path"),
textOutput("txt")
)
server <- function(input,output,session){
val = reactiveVal()
observeEvent(input$get, {
val(file.choose())
})
output$txt <- renderText(val())
}
shinyApp(ui = ui, server = server)
The file.choose dialog returns the file path of a local file. This value is passed to the app where it is stored in a reactiveVal. The app can now display this text string.
Note that the file chosen is local to the browser, not the server. This means that in most cases your server will not be able to access the file.
Also, important to be clear to the user that they are providing a file path, not the file. Some users will not want to share the file path for privacy or security reasons.
Inspired by shinyFiles, I developed the following file-selection modal. This removes a package dependency, allows me to control the root folder users can access, and gives me control of the file-access source code. All of which will help when discussing the app with IT/security.
The file-access modal as a shiny module
## user interface
module_UI = function(id){
ns = NS(id)
tagList(
actionButton(ns("button_open"), "Select file")
)
}
## server
module_Server = function(id){
moduleServer(id, function(input, output, session){
ns = NS(id)
# path setup
root_path = "/set/this/path/so/users/will/only/have/access/to/subfolders"
current_path = reactiveVal()
current_path(root_path)
return_path = reactiveVal()
return_path(root_path)
# Selection modal
observeEvent(input$button_open, {
showModal(
modalDialog(
title = "Select a file",
p(strong("Current path: "), textOutput(ns("current_path"), inline = TRUE)),
fluidRow(
column(2, actionButton(ns("button_back"), "Back")),
column(4, selectInput(ns("dir"), label = NULL, choices = "Please select"))
),
footer = tagList(
modalButton("Cancel"),
actionButton(ns("ok"), "OK")
)
)
)
new_choices = c("Please select", dir(current_path()))
updateSelectInput(inputId = "dir", choices = new_choices)
})
# back button
observeEvent(input$button_back, {
if(current_path() != root_path){
current_path(dirname(current_path()))
new_choices = c("Please select", dir(current_path()))
updateSelectInput(inputId = "dir", choices = new_choices)
}
})
# OK button
observeEvent(input$ok, {
return_path(current_path())
removeModal()
})
# update directory
observeEvent(input$dir, {
if(input$dir != "Please select"){
current_path(file.path(current_path(), input$dir))
}
new_choices = c("Please select", dir(current_path()))
updateSelectInput(inputId = "dir", choices = new_choices)
})
# display directory
output$current_path = renderText({ current_path() })
return(reactive(return_path()))
})
}
A basic app for testing
testApp = function(...){
ui = fluidPage(
module_UI("id"),
textOutput("display")
)
server = function(input, output, session){
selected_path = module_Server("id")
output$display = renderText({ selected_path() })
}
shinyApp(ui, server, ...)
}
# run app
print(testApp())
Note that testing of the root_path is recommended, because some locations can be referred to in multiple ways. For example: dirname("~/Network-Shares/folder/subfolder") = "/home/ORG/username/Network-Shares/folder"