iod
iod

Reputation: 7592

Why doesn't my Shiny (R) actionButton respond after I use a different actionLink?

I'm writing a Shinyapp that enables users, among other things, to input new entries to a mongodb and delete specific rows from it.

I'm trying to add a functionality that would allow to undo the last delete by saving a temporary copy of the row. It seems to work fine, but after I use undo, for some reason the delete button doesn't work anymore, and I can't figure out why.

I thought maybe it has something to do with the fact that there's a few other places where I use observers for the two buttons, but I don't understand why that would cause any problem (and I need them for the app to function properly) - at any rate, they don't prevent me from deleting several rows one after the other so long as I don't use the undo function.

As you can see from the code below, I've put a bunch of print() functions throughout it to try and figure out where it's going. The weird thing - none of them show up! It's like the delete button simply doesn't activate the script once undo was used. Any ideas why?

UPDATE: Here's a short version of server.R and ui.R that reproduces the problem (without using mongodb):

server.R

tempEntry<-NULL
shinyServer(function(input, output, session) {
    dat<-data.frame(nums=1:3,ltrs=c("a","b","c"))
    ## Action: Delete entry
    output$delError<-renderText({
        input$delButton
        isolate({if (!is.na(input$delNum)) {
            tempEntry<<-dat[input$delNum,]
            output$undo<<-renderUI({
                 actionLink("undo","Undo last delete")
                })
            dat<<-dat[-input$delNum,]
            print("deleted")
            print(dat)
        } else print("nope2")
        })
    })

    ## Action: Undo delete
    output$undoError<-renderText({
        input$undo
        if (!is.null(input$undo)) {
        if (input$undo>0) {
        isolate({if (!is.null(tempEntry)) {
            dat<<-rbind(dat,tempEntry)
            tempEntry<<-NULL
            output$delError<<-renderText({""})
            print(dat)
        } else print("nope3")
        }) } else print("undo==0") } else print("undo null")
    })
})

ui.R:

library(shiny)
shinyUI(navbarPage("example",
                   tabPanel("moo",
                    titlePanel(""),
                    fluidPage(numericInput("delNum","Row to delete",value=NULL),
                             actionButton("delButton","Delete row"),
                             uiOutput("undo"),
                             div(p(textOutput("delError")),style="color:red"),
                             div(p(textOutput("undoError")),style="color:blue")
                             ))))          

(This also gives an error "argument 1 (type 'list') cannot be handled by 'cat'" after deleting a row, I don't know why... But the problem doesn't seem to be related to that).

Thanks!

Upvotes: 1

Views: 1575

Answers (1)

Marat Talipov
Marat Talipov

Reputation: 13314

That happens because of the output$delError<<-renderText({""}) code that overwrites the original output$delError expression by the empty one, so no surprise output$delError does not trigger on input$delButton any more.


[UPDATE]

The OP's application uses actionButton and actionLink to delete and undelete records from a database, respectively. The 'delete' button is supposed to trigger the delError expression that deletes the record and shows the outcome of deletion (e.g. 'record deleted'). Similarly, the 'undelete' button triggers the undoError expression that puts the record back into the table and reports an outcome of undeletion (e.g. 'record undeleted'). The problem is that undoError has to get rid of the output produced by delError because outputs 'record deleted' and 'record undeleted' don't make much sense when they appear together, but the output 'record deleted' can be removed only by the delError expression.

It seems that this problem can be resolved by modifying delError to make it hide its output when the 'undelete' button (or link) is pressed. But in this case, delError would trigger on both 'delete' and 'undelete' buttons without being able to say which button caused the evaluation, so it would try to delete a record when the 'undelete' button is pressed!

The sample application below provides a way to address this problem by using a global variable that stores the status of the last operation. This status is generated by two high-priority observers (one for 'delete' and another for 'undelete'), which also take care of actual deleting/undeleting of the record. The observers don't produce output that directly goes to the web page, so there is no hassle with getting rid of the messages produced by the other observer. Instead, the status variable is shown by a simple reactive expression.

server.R

tempEntry<-NULL
dat<-data.frame(nums=1:3,ltrs=c("a","b","c"))


shinyServer(function(input, output, session) {

    del.status <- NULL


    ##################
    ### Observers ####
    ##################

    delete.row <- observe({
        if (input$delButton ==0 ) return()  # we don't want to delete anything at start

        delNum <- isolate( input$delNum ) # this is the only thing that needs to be isolated
        if (is.na(delNum)) { 
            print('nope2')
            return() 
        }

        tempEntry <<- dat[delNum,]
        dat <<- dat[-delNum,]

        output$undo <<- renderUI( actionLink("undo","Undo last delete") )
        del.status <<- 'deleted'
    },priority=100) # make sure that del.status will be updated *before* the evaluation of output$delError



    undelete.row <- observe({
        if (is.null(input$undo) || input$undo==0) return() # trigger on undowe don't want to undelete anything at the beginning of the script

        dat <<- rbind(dat,tempEntry)
        tempEntry <<- NULL

        output$undo <<- renderUI("")
        del.status <<- 'undeleted'
    },priority=100)



    ##################
    ### Renderers ####
    ##################

    output$delError <- renderText({
        if (input$delButton == 0) return() # show nothing until first deletion
        input$undo                         # trigger on undo

        return(del.status)
    })

    output$show.table <- renderTable({ 
        input$delButton; input$undo        # trigger on delete/undelete buttons
        return(dat)
    })

})

ui.R

library(shiny)
shinyUI(
    navbarPage(
         "example"
       , tabPanel("moo"
       , titlePanel("")
       , fluidPage(
             numericInput("delNum","Row to delete",value=NULL)
            , div(p(textOutput("delError")),style="color:red")
            , actionButton("delButton","Delete row")
            , uiOutput("undo")
            , tableOutput('show.table')
            )
       )
    )
) 

Upvotes: 1

Related Questions