29

I want to create an input file dialog. This is straightforward using the fileInput function.

shinyUI(pageWithSidebar(
  headerPanel(""),
  sidebarPanel(
    fileInput("file", "Select a file")  
  ),
  mainPanel()
))

enter image description here

After uploading it looks like this: enter image description here

Now, I want to reset the inputFile element to the state it had before uploading. As there is no such function like updateFileInput, me being a JS/HTML rookie, I am not able to figure out how I could achieve that. The code output from fileInput("file", "Select a file") is the following.

<label>Select a file</label>
<input id="file" type="file" accept="text/plain"/>
<div id="file_progress" class="progress progress-striped active shiny-file-input-progress">
  <div class="bar"></div>
  <label></label>
</div> 

Any ideas?

PS. I do not want to use a reactive renderUI here to re-render the file input element. I'd rather want to go the 'update way' (if there is such a thing) ...

Mark Heckmann
  • 10,943
  • 4
  • 56
  • 88
  • this `shinyjs` approach is very easy compared to the others: https://gist.github.com/bborgesr/07406b30ade8a011e59971835bf6c6f7 – Roman Sep 19 '19 at 08:48

5 Answers5

9

As @Julien Navarre pointed out, this boils down to modifying some HTML/CSS. Julien showed how to do that from the client side. What remains to be shown is how to do that from the server side. I.e. the server will invoke a function on the client side that will set back the input handler. You can find a blog post on passing data between server and client using shiny here.

On the server side the crucial function is session$sendCustomMessage which will call a the handler function resetFileInputHandler on the client side. The id of the file input object is passed to the handler.

server.R

shinyServer(function(input, output, session) {

  observe({
    input$btn
    session$sendCustomMessage(type = "resetFileInputHandler", "file1") 
  })

})

Now, on the client side we need to register a handler function that will be called by the server and perform the necessary changes as outlined by Julien.

ui.R

shinyUI(bootstrapPage(

  fileInput('file1', 'Choose File'),
  actionButton("btn", "Trigger server to reset file input"),

  tags$script('
    Shiny.addCustomMessageHandler("resetFileInputHandler", function(x) {      
        var id = "#" + x + "_progress";      # name of progress bar is file1_progress
        var idBar = id + " .bar";  
        $(id).css("visibility", "hidden");   # change visibility
        $(idBar).css("width", "0%");         # reset bar to 0%
    });
  ')
))

Pressing the button will now cause the server to invoke the resetFileInputHandler on the client side (of course the button is just for demo puposes).

You can find the above code here or run it like this

library(shiny)
runGist("8314905")

Caution

This solution leaves on aspect untouched:The file name shown to the right for the shiny object

<input id="file1" type="file" class="shiny-bound-input">

is not altered. I guess that would mean digging deeper into it. Suggestions are welcome.

Mark Heckmann
  • 10,943
  • 4
  • 56
  • 88
5

maybe it's too late, but if you still need it,

When the bar appears you can see a new "style" attribute appear :

<div id="file1_progress" class="progress progress-striped shiny-file-input-progress" style="visibility: visible;">

All you have to do with JS, is to get the element with his id ("file1_progress") and to set the parameter "visibility" of the "style" attribute to "hidden".

I put an example on Gist, you can see it (and the code) by running the following command :

shiny::runGist('8306992')

Hope this help.

Julien Navarre
  • 7,653
  • 3
  • 42
  • 69
  • Thanks for the hint. Below I also set back the filling of the progress bar. ONe problem remains though: How to reset the `` part? Any ideas? – Mark Heckmann Jan 08 '14 at 11:06
4

Here are two ways of resetting the file input UI (both the progress bar and the displayed name). The first uses JavaScript and the second uses renderUI.

The examples include both a "Clear" button as well as an optional drop-down menu that resets the file input UI when the selection changes.

Example 1 - using JavaScript

ui.R

shinyUI(bootstrapPage(

    tags$head(
        tags$style(".clearButton {float:right; font-size:12px;}")
    ),

    headerPanel("Reset file input example"),

    sidebarPanel(
        HTML("<button id='clearFile1' class='action-button clearButton'>Clear</button>"),
        fileInput('file1', NULL, width="80%"),

        selectInput('uploadFormat', label = "Select upload format", 
            choices = c(
                "Option 1" = 'f1',
                "Option 2" = 'f2',
                "Option 3" = 'f3'),
            selected = 'f1')
    ),

    mainPanel(
        h4("Summary"),
        verbatimTextOutput("summary")
    ),
    singleton(includeScript("active.js"))
))

server.R

shinyServer(function(input, output, session) {

    values <- reactiveValues(
        file1 = NULL
    )

    observe({
        input$clearFile1
        input$uploadFormat
        values$file1 <- NULL
    })

    observe({
        values$file1 <- input$file1
    })

    output$summary <- renderText({
        return(paste("Uploaded file: ", values$file1$name))
    })

})

active.js

$(document).ready(function() {

    /* clear file button control */
    var fileControl = $("#file1");

    $("#clearFile1").on("click", function () {
        fileControl.replaceWith( fileControl = fileControl.clone( true ) );
        $("#file1_progress").hide();
    });

    $("#uploadFormat").on("change", function () {
        fileControl.replaceWith( fileControl = fileControl.clone( true ) );
        $("#file1_progress").hide();
    });

    /* file input progress bar control */
    $( "#file1" ).change(function() {
      document.getElementById("file1_progress").setAttribute('style', "height:20px; margin-top:5px;");
    });

});

Example 2 - using renderUI

Same as above, but (1) get rid of active.js and the related include statement, (2) in ui.R replace

fileInput('file1', NULL, width="80%"),

with

uiOutput('resettableInput'),

and (3) in server.R add:

    output$resettableInput <- renderUI({
        input$clearFile1
        input$uploadFormat

        fileInput('file1', NULL, width="80%")
    })

Note that one could enclose multiple UI elements in a list() statement here and they would all be re-rendered. See here.

You can find the code for these examples here and here, and run them like so:

library(shiny)
shiny::runGist('0c2a98a0236f1257fd45')
shiny::runGist('bc09d77fa92457e094c8')
Community
  • 1
  • 1
DavidArndt
  • 406
  • 2
  • 6
4

For anyone who needs this functionality in the future: you can use the reset() function from the shinyjs package (since version 0.8) https://github.com/daattali/shinyjs

DeanAttali
  • 25,268
  • 10
  • 92
  • 118
  • 3
    Thanks daattali. I tried your code and it resets the `fileInput()`, but I still find files by `input$files`. How to actually reset input$files. – Agaz Wani Dec 10 '16 at 10:43
  • Any news about reset of files which are at input$files?? – AwaitedOne Jun 08 '17 at 06:03
  • Earlier this week I submitted a PR to shiny that does some work for this. I think it will be in shiny by the end of the month. – DeanAttali Jun 08 '17 at 06:31
1

Not the best option but below you can find my quick solution which I use.

My system allows to make comments and upload files for each entry in the database. So when the user uploads and save the fileA for entryX and then pass to entryY... by mistake if the user clicks on save button the same file (fileA) will be also saved for the entryY. To prevent this here my quick solution:

After saving fileA delete the datapath and before saving a file check if datapath exists;

system(paste0("rm ",input$filenote$datapath))

file.exists(input$filenote$datapath)

But if the user really wants to save the same file (fileA) for entryX and entryY then he/she has to first upload fileB and then again fileA and then click to save... If you select again fileA after deleting input$filenote$datapath it does not find the fileA....