Fabrice
Fabrice

Reputation: 387

Ghost element after removeUI

I resolved a problem thanks to stackoverflow but I want to submit it to you because I don't know the reason why it doesn't work at first time.

I want to print a list of items in a modalDialog with a minus icon in front of each item in order to remove this item.

I use removeUI to do that because in the shiny doc it says "This function allows you to remove any part of your UI. Once removeUI is executed on some element, it is gone forever." (https://shiny.rstudio.com/reference/shiny/1.1.0/removeui)

FOREVER !

But GONE doesn't mean "NOT EXISTS ANYMORE" ???

After clicking on the Modal button, if I remove the second element of my list by clicking the minus icon and loop over the items when clicking the SAVE button, we can see in the console that all elements are always here !

So removeUI looks like a shiny::hide from shinyjs library...

To remove the element, the solution found here How to clean "input" after removing element using removeUI is to set each id value to NULL but its a little bit "brut force" method...

So is there a REAL removeUI function (and not a simple hideUI !!!) ?

Thanx !

You may find below a reproducible code with the stackoverflow workaround (uncomment it to make it available)

library(shiny)
#library(shinyjs)

ui <- fluidPage(
  #useShinyjs(),
  actionButton("open", "Modal")
)

server <- function(input, output, session) {
  
  myvals <- reactiveVal(
    data.frame(item=c(1,2,3),value=c("value1","value2","value3"))
  )

    observeEvent(input$open, {
    showModal(
      modalDialog(
        tagList(
          div(id="choice")
        ),
        footer = tagList(
          modalButton("cancel"),
          actionButton("save", "SAVE")
        )
      )
    )
      add_items()
  })
  
  add_items <- function() {
    lapply(1:nrow(myvals()), function(row) {
      insertUI(selector = "#choice", ui = add_item(row, myvals()[row,]$item, myvals()[row,]$value))
       observeEvent(input[[paste0("bu",row)]], {
         removeUI(selector = paste0("#choice",row))
         #runjs(paste0("Shiny.onInputChange('",paste0("item",row),"',null)"))
         #runjs(paste0("Shiny.onInputChange('",paste0("value",row),"',null)"))
        })
    }
  )
}
    
  add_item <- function(row, item, value) {
    return(
      tagList(
        div( id=paste0("choice",row),
            actionButton(inputId = paste0("bu",row), icon("minus")),
            div(textInput(inputId = paste0("item",row), "ID", value=item)),
            div(textInput(inputId = paste0("value",row), "Value",value=value))
        )
      )
    )
  }
  
  observeEvent(input$save, {
    for (i in 1:3) {
      print(paste("item=", input[[paste0("item",i)]], "value=", input[[paste0("value",i)]]))
    }
  })
}

shinyApp(ui, server)

Upvotes: 1

Views: 60

Answers (1)

ismirsehregal
ismirsehregal

Reputation: 33417

I guess for now you'll need to stick with the workaround, please see this related GitHub issue.

I used a similar approach in my earlier answer here.

Regarding Shiny.onInputChange please note:

If you have heard of a function called Shiny.onInputChange, that’s just an older, more confusing name for Shiny.setInputValue; the latter was introduced in Shiny v1.1. Despite never being officially documented or supported, Shiny.onInputChange was/is widely used and we’re not likely to remove it anytime soon, and its behavior is identical to Shiny.setInputValue.

library(shiny)
library(shinyjs)

ui <- fluidPage(useShinyjs(), actionButton("open", "Modal"))

server <- function(input, output, session) {
  myvals <- reactiveVal(data.frame(
    item = c(1, 2, 3),
    value = c("value1", "value2", "value3")
  ))
  
  observeEvent(input$open, {
    showModal(modalDialog(tagList(div(id = "choice")),
                          footer = tagList(
                            modalButton("cancel"),
                            actionButton("save", "SAVE")
                          )))
    add_items()
  })
  
  add_items <- function() {
    lapply(1:nrow(myvals()), function(row) {
      insertUI(selector = "#choice", ui = add_item(row, myvals()[row, ]$item, myvals()[row, ]$value))
      
      observeEvent(input[[paste0("bu", row)]], {
        removeUI(selector = paste0("#choice", row))
        runjs(
          sprintf(
            'Shiny.setInputValue("%s", null, {priority: "event"});
             Shiny.setInputValue("%s", null, {priority: "event"});
             Shiny.setInputValue("%s", null, {priority: "event"});',
            paste0("bu", row),
            paste0("item", row),
            paste0("value", row)
          )
        )
        # runjs(sprintf('Shiny.setInputValue("%s", null, {priority: "event"});', paste0("bu", row)))
        # runjs(sprintf('Shiny.setInputValue("%s", null, {priority: "event"});', paste0("item", row)))
        # runjs(sprintf('Shiny.setInputValue("%s", null, {priority: "event"});', paste0("value", row)))
      })
    })
  }
  
  add_item <- function(row, item, value) {
    return(tagList(div(
      id = paste0("choice", row),
      actionButton(inputId = paste0("bu", row), icon("minus")),
      div(textInput(inputId = paste0("item", row), "ID", value = item)),
      div(textInput(inputId = paste0("value", row), "Value", value = value))
    )))
  }
  
  observeEvent(input$save, {
    for (i in 1:3) {
      print(paste("item=", input[[paste0("item", i)]], "value=", input[[paste0("value", i)]]))
    }
  })
}

shinyApp(ui, server)

Simplified version of your initial code:

library(shiny)
library(shinyjs)

ui <- fluidPage(
  useShinyjs(),
  actionButton("open", "Modal")
)

server <- function(input, output, session) {
  
  myvals <- reactiveVal(
    data.frame(item=c(1,2,3),value=c("value1","value2","value3"))
  )
  
  observeEvent(input$open, {
    showModal(
      modalDialog(
        tagList(
          div(id="choice")
        ),
        footer = tagList(
          modalButton("cancel"),
          actionButton("save", "SAVE")
        )
      )
    )
    add_items()
  })
  
  add_items <- function() {
    lapply(1:nrow(myvals()), function(row) {
      insertUI(selector = "#choice", ui = add_item(row, myvals()[row,]$item, myvals()[row,]$value))
      observeEvent(input[[paste0("bu",row)]], {
        removeUI(selector = paste0("#choice",row))
        runjs(paste0("Shiny.onInputChange('bu", row,"', null)"))
        runjs(paste0("Shiny.onInputChange('item", row,"', null)"))
        runjs(paste0("Shiny.onInputChange('value", row,"', null)"))
      })
    }
    )
  }
  
  add_item <- function(row, item, value) {
    return(
      tagList(
        div( id=paste0("choice",row),
             actionButton(inputId = paste0("bu",row), icon("minus")),
             div(textInput(inputId = paste0("item",row), "ID", value=item)),
             div(textInput(inputId = paste0("value",row), "Value",value=value))
        )
      )
    )
  }
  
  observeEvent(input$save, {
    for (i in 1:3) {
      print(paste("item=", input[[paste0("item",i)]], "value=", input[[paste0("value",i)]]))
    }
  })
}

shinyApp(ui, server)

Upvotes: 1

Related Questions