1
votes

I have a Shiny app that use a fileInput to get some files client-side (I cannot use shinyFiles package that manages files server-side).

I want the user to be only able to upload files matching a specific pattern (e.g. helloWorld.txt) not only matching a file type (e.g. text, csv, etc.).

fileInput has an accept argument where you can provide accepted file types. From the doc:

accept  A character vector of MIME types; gives the browser a hint of 
        what kind of files the server is expecting.

I do not just want to specify accepted file types, which is not restrictive enough for my app. Is there a way to do this?

Here is a MWE to accept only text files:

library(shiny)

ui <- fluidPage(
    fileInput(
        "file_choice",
        label = "Choose a files", 
        multiple = TRUE,
        accept = c(
            ".txt"
        )
    )
)

server <- function(input, output, session) {}

shinyApp(ui, server)

If I use:

accept = c(
    "helloWorld.txt"
)

It does not work because it is not a MIME type.

This page Shiny fileInput parameter "accept" issue proposes to handle the selected file afterward server-side, which is what I will end up doing, but I would prefer a restriction a priori and not a posteriori (to avoid the server-side file checking and feedback to user).

1
I've never seen any browser-based file dialog capable of doing this, whether shiny or anything else (even native). Are you sure the capability exists? If you can find a javascript implementation, you should be able to get shiny to use it (somehow, perhaps through shinyjs), but ... the only times I've seen it do that level of filtering/control is when the dialog is app-specific, written for that purpose.r2evans
But ... perhaps you're asking if there's a way to interrupt the upload process -- after the file-dialog closes (with one or more files selected), run some check on the file names before allowing the client browser to upload them. Is that it? (I don't know how to do that, I'm just trying to wrap my brain around this.)r2evans
@r2evans I didn't event know if this was possible or not to filter files a priori. Eventually, I will manage this server-side after the upload, I don't know if I can stop the upload process after the the file-dialog closes (that would be a great solution actually), I will look into it. Thanks.Odin

1 Answers

1
votes

One method is to interject some javascript as an onchange event trigger that checks the filename and, if it doesn't match, interrupt the upload process. This method uses an alert, I know many consider this method to be a bit invasive and not great aesthetics, I'm sure others can make better suggestions.

I should start with a simple caveat: the conditional here is strictly "the filename begins with the literal hello". Your example might require a little more finesse, instead requiring the filename sans-extension to match. In that case, regular expressions might be in order, reference something like https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions for more info. What this answer provides is a framework for you to fill in the holes.

(While modifying this code, you might find it useful to add alert(FileName) and/or alert(FileBase) to see what javascript is comparing against your pattern. It will popup with every attempt. In my case here, it helped me discover that, not surprisingly, the windows path was present, meaning it was using backslashes instead of forward-slashes, necessitating the split(/[\\/]/), further escaped for R.)

checkjs <- 'function checkFileName(fieldObj) {
    var FileName  = fieldObj.value;
    var FileBase = FileName.split(/[\\\\/]/).pop();
    if (! FileBase.startsWith("hello")) {
        fieldObj.value = "";
        alert("File does not start with hello");
        return false;
    }
    return true;
}'

attrib_replace <- function(x, cond, ...) {
  if (all(names(cond) %in% names(x)) && identical(cond, x[names(cond)])) x <- c(x, list(...))
  if ("attribs" %in% names(x)) x$attribs <- attrib_replace(x$attribs, cond = cond, ...)
  if ("children" %in% names(x)) x$children <- lapply(x$children, function(ch) attrib_replace(ch, cond = cond, ...))
  x
}

A sample app, using this:

library(shiny)
shinyApp(
  ui = fluidPage(
    tags$script(checkjs),
    attrib_replace(fileInput(
        "file_choice",
        label = "Choose a file",
        multiple = TRUE,
        accept = c(".txt")
    ), list(id = "file_choice", type = "file"), onchange = "checkFileName(this);")
  ),
  server = function(input, output, session) {}
)

shiny app showing interrupt file upload

When you select a file that does not start with "hello", it gives an alert and does not upload the file. A proper file uploads just file.

Some other answers I referenced for this: