Interactive directory input in Shiny app (R) - r

I am building a shiny app that requires a user to select a folder on the local machine, which contains the files to be processed by the app.
I am using a solution proposed here. This works fine on a local machine, but does not work if the app is deployed to a shinyapps server.
The author of this solution confirmed that it was only designed to work with local Shiny apps, since it makes OS shell calls to display a directory dialog.
I am wondering if there is a different solution for directory dialog, which will work on the deployed Shiny apps (I am deploying to shinyapps.io).
Edited: Notice that I cannot use fileInput interface for two reasons:
The users of the app are not technical people and they do not know which files inside the folder are used by the app.
The selected folder may contain other folders within which the needed files reside, such that it is impossible to select all files at once, even if the fileInput interface has the multiple option enabled.
The folder/files structure is not something I can change, it is downloaded AS IS from a medical device and therefore the only thing I can expect from the users is to specify the parent folder and the rest should be done inside the R code.

This is a working example based on using the "webkitdirectory" attribute. At the moment the attribute is supported by Chrome, Opera and Safari (mobile and desktop) and it should be supported in Firefox 49 to be released in September.
More about this here. It work with subdirectories also.
It requires using the tags keyword in ui.R. I have tested it by uploading three csv files each contaning three numbers separeted by a coma. Tested locally and on shinyapps.io with Chrome and Opera. This is the code:
ui.R
library(shiny)
library(DT)
shinyUI(tagList(fluidPage(theme = "bootstrap.css",
includeScript("./www/text.js"),
titlePanel("Folder content upload"),
fluidRow(
column(4,
wellPanel(
tags$div(class="form-group shiny-input-container",
tags$div(tags$label("File input")),
tags$div(tags$label("Choose folder", class="btn btn-primary",
tags$input(id = "fileIn", webkitdirectory = TRUE, type = "file", style="display: none;", onchange="pressed()"))),
tags$label("No folder choosen", id = "noFile"),
tags$div(id="fileIn_progress", class="progress progress-striped active shiny-file-input-progress",
tags$div(class="progress-bar")
)
),
verbatimTextOutput("results")
)
),
column(8,
tabsetPanel(
tabPanel("Files table", dataTableOutput("tbl")),
tabPanel("Files list", dataTableOutput("tbl2"))
)
)
)
),
HTML("<script type='text/javascript' src='getFolders.js'></script>")
)
)
server.R
library(shiny)
library(ggplot2)
library(DT)
shinyServer(function(input, output, session) {
df <- reactive({
inFiles <- input$fileIn
df <- data.frame()
if (is.null(inFiles))
return(NULL)
for (i in seq_along(inFiles$datapath)) {
tmp <- read.csv(inFiles$datapath[i], header = FALSE)
df <- rbind(df, tmp)
}
df
})
output$tbl <- DT::renderDataTable(
df()
)
output$tbl2 <- DT::renderDataTable(
input$fileIn
)
output$results = renderPrint({
input$mydata
})
})
text.js
window.pressed = function(){
var a = document.getElementById('fileIn');
if(a.value === "")
{
noFile.innerHTML = "No folder choosen";
}
else
{
noFile.innerHTML = "";
}
};
getFolders.js
document.getElementById("fileIn").addEventListener("change", function(e) {
let files = e.target.files;
var arr = new Array(files.length*2);
for (let i=0; i<files.length; i++) {
//console.log(files[i].webkitRelativePath);
//console.log(files[i].name);
arr[i] = files[i].webkitRelativePath;
arr[i+files.length] = files[i].name;
}
Shiny.onInputChange("mydata", arr);
});
Let me know if this helps.

Have you tried around with the shinyFiles package?
There is a widget which lets you chose a directory.
As output you get the path of that directory which you can in turn use to access the files.
Here is an example how it works.
server
library(shiny)
library(shinyFiles)
shinyServer(function(input, output, session) {
# dir
shinyDirChoose(input, 'dir', roots = c(home = '~'), filetypes = c('', 'txt'))
dir <- reactive(input$dir)
output$dir <- renderPrint(dir())
# path
path <- reactive({
home <- normalizePath("~")
file.path(home, paste(unlist(dir()$path[-1]), collapse = .Platform$file.sep))
})
# files
output$files <- renderPrint(list.files(path()))
})
ui
library(shiny)
library(shinyFiles)
shinyUI(fluidPage(sidebarLayout(
sidebarPanel(
shinyDirButton("dir", "Chose directory", "Upload")
),
mainPanel(
h4("output$dir"),
verbatimTextOutput("dir"), br(),
h4("Files in that dir"),
verbatimTextOutput("files")
)
)))
Hope this helps.

Related

Navigate client-side or server-side file system in Shiny to select folder

I would like to allow users of my app to navigate either the file system on their computer or a specific volume on the hosting server. ShinyFiles allows for a connection to the server, but is there a way to allow Shiny to access a directory on the user's computer when not run locally? It would be great to have a selectable UI element that would give the choice of which file system to look in. This could be similar to how the shinyDirChoose function behaves when passing multiple "roots" in, allowing selection of different parts of the folder structure. I would like to be able to select a whole directory, not just single files if possible.
Here is a reproducible example that shows the directory navigation portion of my app.
library(shiny)
library(shinyFiles)
ui <- shinyUI(fluidPage(
shinyDirButton(id='directory', label='Folder select', title='Please select a folder'),
column(1, offset = 11,
actionButton("exitButton", "Exit")), #kill the app
mainPanel(
tags$h4("Folder selected: ", verbatimTextOutput("directorypath")), # print out the path of the selected folder
width = 8),
))
server <- shinyServer(function(input, output, session) {
volumes <- c(Home = fs::path_home())
shinyDirChoose(input=input, id="directory", roots = volumes, session = session, restrictions = system.file(package = "base"))
output$directorypath <- renderPrint({
if (is.integer(input$directory)) {
cat("No directory has been selected")
} else {
cat(parseDirPath(roots=volumes, selection=input$directory))
}
})
observeEvent(input$exitButton, {
stopApp()
})
})
shinyApp(ui, server)

Excel data distribution with Shiny

I am not good at English, so sentences may be wrong.
I want to distribute excel files prepared in advance to users. Is it possible to realize such a system with shiny? No problem with .zip.
Thank you
ui.R
shinyUI(
fluidPage(
downloadButton('downloadData', 'Excel Download')
)
)
server.R
shinyServer(function(input, output) {
output$downloadData <- downloadHandler(
filename = "distribution.xlsx",
content = "distribution_excel"
)
})
Yes, it is possible and you were nearly there. Below is a minimal working example. I assume that your .xlsx file is located in the same folder as your app.R. Notice that I have created the app in a single R file as opposed to two separate files.
The trick to getting the file to download is to use a function for the content inside of downloadHandler(). Specifically we are using the base function file.copy(). Clicking the button should now download the file: distribution.xlsx. This file can obviously be exchanged with a zip file.
If you want different users to access different Excel files, you can write an additional function inside of your server function that passes the file argument to the downloadHandler().
# Load packages ----
pkgs <- c("shiny")
invisible(lapply(pkgs, require, character.only = TRUE))
# Set up the UI ----
ui <- fluidPage(
# Define your download button
downloadButton(
outputId = "downloadData",
label = "Excel Download"
)
)
# Set up the server side ----
server <- function(input, output, session) {
# Define the download handler with function() for content.
output$downloadData <- downloadHandler(
filename = "distribution.xlsx",
content = function(file) {
file.copy("distribution.xlsx", file)
}
)
}
# Combine into an app ----
shinyApp(ui = ui, server = server)

Display images from web in shiny R

I am trying to make a Shiny App that retrieves an image from Nasa API and displays it to the user.
Although I manage to download the image from the API and store it in a temp file I can't display it in the shiny app but only locally.
Here is my code so far:
library(shiny)
library(httr)
library(jpeg)
library(RCurl)
library(jsonlite)
library(shinythemes)
#library(imager)
key<-"eH45R9w40U4mHE79ErvPWMtaANJlDwNaEtGx3vLF"
url<-"https://api.nasa.gov/planetary/apod?date="
ui <- fluidPage(theme = shinytheme("yeti"),
# Application title
titlePanel("Nasa API"),
sidebarLayout(
sidebarPanel(
helpText("Wellcome to Nasa search API ",
"enter a date in YYYY-MM-DD to search for picture"),
textInput("date", label="Date input",
value = "Enter date..."),
actionButton("go", "Search")
),
mainPanel(
imageOutput("myImage")
)
)
)
server <- function(input, output,session) {
query<-eventReactive(input$go,{
input$date
})
output$myImage <- renderImage({
nasa_url<-paste0(url,query(),"&api_key=",key)
# A temp file to save the output.
# This file will be removed later by renderImage
response<-getURLContent(nasa_url)
json<-fromJSON(response)
img_url<-json$url
temp<-tempfile(pattern = "file", fileext = ".jpg")
download.file(img_url,temp,mode="wb")
jj <- readJPEG(temp,native=TRUE)
plot(0:1,0:1,type="n",ann=FALSE,axes=FALSE)
rasterImage(jj,0,0,1,1)
#im<-load.image(temp) #use this with library(imager)
#plot(im) #use this with library(imager)
},deleteFile = T)
}
# Run the application
shinyApp(ui = ui, server = server)
Be careful when sharing your code as you just shared your private API key. I suggest you generate a new one.
It does not work because shiny only serves files that are in the ~/www directory. So they should be downloaded to that folder for your method to work.
Perhaps an easier way to go about this is simply to embed the image. Looking at the code it looks like json$url is the URL to the image.
library(shiny)
ui <- fluidPage(
h4("Embedded image"),
uiOutput("img")
)
server <- function(input, output, session) {
output$img <- renderUI({
tags$img(src = "https://www.r-project.org/logo/Rlogo.png")
})
}
shinyApp(ui, server)
You could try the above without hardcoding https://www.r-project.org/logo/Rlogo.png and using your json$url instead.

have Rshiny app user upload image to dropbox /shiny server / google drive

I am almost done creating a survey using Rshiny. I want to create a button that will allow the user to upload an image (jpeg,tiff,png etc) to the R shiny server, Dropbox, or google drive. It seems the fileInput method only accepts documents like csv? any help would be appreciated!
fileInput actually allows the import of any type of files. You just need to set the argument accept to NULL or to any file extension that you accept. What you need to understand first, is that if you use fileInput, it will actually upload your file to the tmp folder (the path to the uploaded data is stored in input$file$datapath), and only then you will be able to upload your file to googledrive or to whatever cloud. A solution using fileInput could look like this:
library(googledrive)
ui <- fluidPage(
fileInput(inputId = "file",
label = "Choose file to upload",
accept = NULL)
)
server <- function(input, output) {
observeEvent(input$file, {
drive_upload(media = input$file$datapath,
name = input$file$name)
})
}
shinyApp(ui, server)
If the "double upload" is a problem for you, you can avoid this by using the package shinyFiles. A nice answer has been posted here and here's a way to adapt the code to address your specific problem.
library(googledrive)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton("Btn_GetFile", "Choose file to upload" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default", class = NULL)
)
server <- function(input, output, session) {
volumes = getVolumes()
observe({
shinyFileChoose(input, "Btn_GetFile", roots = volumes, session = session)
if (!is.null(input$Btn_GetFile)){
file_selected <- parseFilePaths(volumes, input$Btn_GetFile)
drive_upload(media = as.character(file_selected$datapath),
name = as.character(file_selected$name))
}
})
}
shinyApp(ui = ui, server = server)

Getting file path from Shiny UI (Not just directory) using browse button without uploading the file

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"

Resources