Search code examples
javascriptrshinyrhandsontable

How to conditionally deactivate or remove selection items in the rhandsontable context menu?


In running the below simplified code, the user can add or remove table rows by right-clicking on a row which, through the rhandsontable package context menu, generates a pop-up of action choices. In the code you can see how I used the onRender(...) function and JavaScript to deactivate row deletion when there is only 1 row in the table. This works.

However, I would also like to either deactivate, or remove (whichever is simpler), the "Insert row above" selection when the user accesses the context menu from the first row of the table. When the user accesses the context menu from any table row other than the first then "Insert row above" should be functional. Basically, I never want a top row that is blank. Any ideas for how to do this?

I went through the handsontable listing of hooks and could not find any equivalent of "beforeAddRow" or the like.

library(shiny)
library(rhandsontable)
library(htmlwidgets)

ui <- fluidPage(
  br(),
  rHandsontableOutput("simple_table")
)

server <- function(input, output) {
  output$simple_table <- renderRHandsontable({
    rhandsontable(data.frame(Value = numeric(1)), contextMenu = TRUE, rowHeaders = TRUE) %>%
      hot_cols(colWidths = 100, type = "numeric") %>%
      # Below prevents row deletion when there is only 1 row in table
      onRender(
        c(
          "function(el, x) {",
          "  var hot = this.hot;",
          "  Handsontable.hooks.add('beforeRemoveRow', function(index, amount){",
          "    var nrows = hot.countRows();",
          "    if(nrows === 1) {",
          "      return false;",
          "    }",
          "  }, hot);",
          "}"
        )
      )
  })
}

shinyApp(ui = ui, server = server)

Solution

  • I think that the best option is to remove the not-needed items from the context menu because otherwise it may be confusing for the user why nothing happens when they click on some items. Here is a solution which does not need custom Javascript. I treat here the situation that you would like to have "Insert row above" and "Remove row" not to be visible if the table has only one row.

    The idea is that inside the renderRHandsontable we also have an assignment rhot$x$contextMenu <- ContextMenuItems(), where ContextMenuItems is a reactive containing the menu items which we update inside an observeEvent on the table. Inside this observeEvent we distinguish between the nrow() == 1 and nrow() > 1 case and set the items respectively.

    enter image description here

    library(shiny)
    library(rhandsontable)
    
    ui <- fluidPage(
      br(),
      rHandsontableOutput("simple_table")
    )
    
    server <- function(input, output) {
      
      ContextMenuItems <- reactiveVal(list(items = c("row_below",
                                                     "---------", "undo", "redo", 
                                                     "---------", "alignment")))
      
      Data <- reactiveVal(data.frame(Value = numeric(1)))
      
      observeEvent(input$simple_table, {
        Data(hot_to_r(input$simple_table))
        nrows_table <- nrow(Data())
        newIt <- ContextMenuItems()
        if (nrows_table == 1) {
          newIt$items <- newIt$items[!(newIt$items %in% c("row_above", "remove_row"))]
        } else if (!(any(c("row_above", "remove_row") %in% newIt$items))) {
          newIt$items <- newIt$items |> append("row_above", 0) |> append("remove_row", 2)
        }
        ContextMenuItems(newIt)
      })
                                      
      output$simple_table <- renderRHandsontable({
        rhot <- rhandsontable(Data(), contextMenu = TRUE, rowHeaders = TRUE) %>%
          hot_cols(colWidths = 100, type = "numeric")
        rhot$x$contextMenu <- ContextMenuItems()
        rhot
      })
    }
    
    shinyApp(ui = ui, server = server)