1

I wanted to extend solution presented here. This code renders dropdowns with options for last column of the dataset and it works perfectly for static dataset.

When I'm adding new row and re-rendering the table dropdown disappears though I have callback variable in renderDT function. Is it possible to have the dropdown for every rendering of the table? What could cause the issue?

Here's my code:

library(shiny)
library(DT)
library(data.table)

callback <- c(
    "var id = $(table.table().node()).closest('.datatables').attr('id');",
    "$.contextMenu({",
    "  selector: '#' + id + ' td.factor input[type=text]',", 
    "  trigger: 'hover',",
    "  build: function($trigger, e){",
    "    var colindex = table.cell($trigger.parent()[0]).index().column;",
    "    var coldata = table.column(colindex).data().unique();",
    "    var options = coldata.reduce(function(result, item, index, array){",
    "      result[index] = item;",
    "      return result;",
    "    }, {});",
    "    return {",
    "      autoHide: true,",
    "      items: {",
    "        dropdown: {",
    "          name: 'Edit',", 
    "          type: 'select',", 
    "          options: options,",
    "          selected: 0", 
    "        }",
    "      },",
    "      events: {",
    "        show: function(opts){",
    "          opts.$trigger.off('blur');",
    "        },",
    "        hide: function(opts){",
    "          var $this = this;",
    "          var data = $.contextMenu.getInputValues(opts, $this.data());",
    "          var $input = opts.$trigger;",
    "          $input.val(options[data.dropdown]);",
    "          $input.trigger('change');",
    "        }",
    "      }",
    "    };",
    "  }",
    "});" 
)
ui <- fluidPage(
    tags$head(
        tags$link(
            rel = "stylesheet", 
            href = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.css"
        ),
        tags$script(
            src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.js"
        )
    ),
    
    sidebarLayout(
        sidebarPanel(actionButton("add", "Add row"),),
        mainPanel(DTOutput("dtable"))
    )
    
)

server <- function(input, output){
    output[["dtable"]] <- renderDT({
        iris2 <<- iris
        datatable(
            iris2, editable = "cell", callback = JS(callback), 
            options = list(
                columnDefs = list(
                    list(
                        targets = 5, className = "factor"
                    )
                )
            )
        )
    }, server = FALSE) 
    
    observeEvent(input$add, {
        new.row <- t(rep(NA, ncol(iris2)))
        colnames(new.row) <- colnames(iris2)
        new.row <- data.table(new.row)
        iris2 <<- rbind(new.row,iris2)
        output$dtable <- renderDT({
            datatable(
                iris2, editable = "cell", 
                callback = JS(callback),
                options = list(
                    columnDefs = list(
                        list(
                            targets = 5 , className = "factor"
                        )
                    )
                )
            )
        }, server = TRUE)
    })
}

shinyApp(ui, server)
Dominik
  • 11
  • 1

1 Answers1

0

Do you know you can use the datatableProxy? It can be used to manipulate an existing datatable instance. This seems to be working

library(shiny)
library(DT)
library(data.table)

callback <- c(
    "var id = $(table.table().node()).closest('.datatables').attr('id');",
    "$.contextMenu({",
    "  selector: '#' + id + ' td.factor input[type=text]',", 
    "  trigger: 'hover',",
    "  build: function($trigger, e){",
    "    var colindex = table.cell($trigger.parent()[0]).index().column;",
    "    var coldata = table.column(colindex).data().unique();",
    "    var options = coldata.reduce(function(result, item, index, array){",
    "      result[index] = item;",
    "      return result;",
    "    }, {});",
    "    return {",
    "      autoHide: true,",
    "      items: {",
    "        dropdown: {",
    "          name: 'Edit',", 
    "          type: 'select',", 
    "          options: options,",
    "          selected: 0", 
    "        }",
    "      },",
    "      events: {",
    "        show: function(opts){",
    "          opts.$trigger.off('blur');",
    "        },",
    "        hide: function(opts){",
    "          var $this = this;",
    "          var data = $.contextMenu.getInputValues(opts, $this.data());",
    "          var $input = opts.$trigger;",
    "          $input.val(options[data.dropdown]);",
    "          $input.trigger('change');",
    "        }",
    "      }",
    "    };",
    "  }",
    "});" 
)
ui <- fluidPage(
    tags$head(
        tags$link(
            rel = "stylesheet", 
            href = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.css"
        ),
        tags$script(
            src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.js"
        )
    ),
    
    sidebarLayout(
        sidebarPanel(actionButton("add", "Add row"),),
        mainPanel(DTOutput("dtable"))
    )
    
)

server <- function(input, output){
    output[["dtable"]] <- renderDT({
        iris2 <<- iris
        datatable(
            iris2, editable = "cell", callback = JS(callback), 
            options = list(
                columnDefs = list(
                    list(
                        targets = 5, className = "factor"
                    )
                )
            )
        )
    }, server = FALSE) 
    
    proxy = dataTableProxy('dtable')
    
    observeEvent(input$add, {
        new.row <- t(rep(NA, ncol(iris2)))
        colnames(new.row) <- colnames(iris2)
        new.row <- data.table(new.row)
        
        proxy %>% addRow(new.row)
    })
}

shinyApp(ui, server)
ginberg
  • 81
  • 9
  • Thank you very much, this solved issue with adding rows. I had a look at the documentation and `reloadData()` with proxy would be very helpful. I need to navigate between different dataframes assigned to `iris2` (with diffenent number of rows and columns, I need to choose the one I want to edit from list) but documentation says that "The data to be reloaded (i.e. the one you pass to `dataTableAjax()`) must have exactly the same number of columns as the previous data object in the table." Do you have an idea how could I solve this issue? – Dominik Feb 25 '21 at 15:47
  • do you know the maximum number of columns at front? Then you could add some dummy columns for the dataframes that have less columns – ginberg Feb 25 '21 at 16:37