Edit datatable in Shiny with dropdown selection for factor variables - r

I am trying to create a Shiny app that allows users to edit a datatable, whereby the edits are saved. Here is a minimal example:
library(shiny)
library(DT)
ui <- fluidPage(
DT::DTOutput('df')
)
server <- function(session, input, output){
df <- data.frame(x = factor(c("A", "B", "C"), levels = c("A", "B", "C")))
output$df <- DT::renderDT(df,
editable = T)
proxy <- dataTableProxy("df")
observeEvent(input$df_cell_edit, {
info <- input$df_cell_edit
str(info)
i <- info$row
j <- info$col
v <- info$value
df[i, j] <<- DT:::coerceValue(v, df[i, j])
replaceData(proxy, df, resetPaging = FALSE)
})
}
shinyApp(ui, server)
This allows me to edit the values of x in-line, but since x is a factor, I'd like to restrict the values that the user is able to input. Ideally, I would like this to be accomplished using a drop-down menu. Is this functionality possible using DT::datatable and Shiny?
Note: I know of the rhandsontable package, however I would prefer to use DT if possible.

As I said in a comment, you can do that with the JS library cellEdit.
Here is another way, using the JS library contextMenu (a jQuery plugin).
library(shiny)
library(DT)
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"
)
),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(
iris, editable = "cell", callback = JS(callback),
options = list(
columnDefs = list(
list(
targets = 5, className = "factor"
)
)
)
)
}, server = FALSE)
}
shinyApp(ui, server)
EDIT
Here is an improvement. In the previous app, the dropdown options are set to the unique values of the column. With the app below, you can set the dropdown options you want.
library(shiny)
library(DT)
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 levels = $trigger.parent().data('levels');",
" if(levels === undefined){",
" var colindex = table.cell($trigger.parent()[0]).index().column;",
" levels = table.column(colindex).data().unique();",
" }",
" var options = levels.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');",
" }",
" }",
" };",
" }",
"});"
)
createdCell <- function(levels){
if(missing(levels)){
return("function(td, cellData, rowData, rowIndex, colIndex){}")
}
quotedLevels <- toString(sprintf("\"%s\"", levels))
c(
"function(td, cellData, rowData, rowIndex, colIndex){",
sprintf(" $(td).attr('data-levels', '[%s]');", quotedLevels),
"}"
)
}
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"
)
),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(
iris, editable = "cell", callback = JS(callback),
options = list(
columnDefs = list(
list(
targets = 5,
className = "factor",
createdCell = JS(createdCell(c(levels(iris$Species), "another level")))
)
)
)
)
}, server = FALSE)
}
shinyApp(ui, server)
If you want to use the unique values of the column, set the option createdCell to JS(createdCell()), or simply don't set this option.

Related

Add dropdown list to every column in a DT table where the values from the dropdown lists are fetched from another dataframe

Based on the very useful reproductive example found here, I have added a dropdown list to every columns of my DT table.
However I'm looking for a way to populate those dropdown lists with values from another dataframe that shares the same column names than the one use in the DT table.
I tried to subset the second dataframe (here "iris2") with the input$dtable_columns_selected but I think I'm missing something here...
My attempt:
library(shiny)
library(DT)
Sepal.Length <- c(10,11,12,13,14)
Sepal.Width <- c(1,2,3,4,5)
Petal.Length <- c(10,11,12,13,14)
Petal.Width <- c(1,2,3,4,5)
Species <- c("SpeciesA", "SpeciesB","SpeciesC", "SpeciesD", "SpeciesE")
iris2 <- data.frame(Sepal.Length, Sepal.Width,Petal.Length,Petal.Width)
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 levels = $trigger.parent().data('levels');",
" if(levels === undefined){",
" var colindex = table.cell($trigger.parent()[0]).index().column;",
" levels = table.column(colindex).data().unique();",
" }",
" var options = levels.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');",
" }",
" }",
" };",
" }",
"});"
)
createdCell <- function(levels){
if(missing(levels)){
return("function(td, cellData, rowData, rowIndex, colIndex){}")
}
quotedLevels <- toString(sprintf("\"%s\"", levels))
c(
"function(td, cellData, rowData, rowIndex, colIndex){",
sprintf(" $(td).attr('data-levels', '[%s]');", quotedLevels),
"}"
)
}
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"
)
),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(
iris, editable = "cell", callback = JS(callback),
options = list(
columnDefs = list(
list(
targets = "_all",
className = "factor",
createdCell = JS(createdCell(c(levels(iris2[,input$dtable_columns_selected]))))
)
)
)
)
}, server = FALSE)
}
shinyApp(ui, server)
This seems to work:
library(shiny)
library(DT)
library(jsonlite)
Sepal.Length <- c(10,11,12,13,14)
Sepal.Width <- c(1,2,3,4,5)
Petal.Length <- c(10,11,12,13,14)
Petal.Width <- c(1,2,3,4,5)
Species <- c("SpeciesA", "SpeciesB", "SpeciesC", "SpeciesD", "SpeciesE")
iris2 <- data.frame(
Sepal.Length,
Sepal.Width,
Petal.Length,
Petal.Width,
Species
)
callback <- c(
"var id = $(table.table().node()).closest('.datatables').attr('id');",
"$.contextMenu({",
" selector: '#' + id + ' td input[type=text]',",
" trigger: 'hover',",
" build: function($trigger, e){",
" var levels = $trigger.parent().data('levels');",
" if(levels === undefined){",
" var colindex = table.cell($trigger.parent()[0]).index().column;",
" levels = table.column(colindex).data().unique();",
" }",
" var options = levels.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.parent().html($input.val());",
" }",
" }",
" };",
" }",
"});"
)
createdCell <- function(dat2){
dat2_json <- toJSON(dat2, dataframe = "values")
c(
"function(td, cellData, rowData, rowIndex, colIndex){",
sprintf(" var matrix = %s;", dat2_json),
" var tmatrix = matrix[0].map((col, i) => matrix.map(row => row[i]));", # we transpose
" $(td).attr('data-levels', JSON.stringify(tmatrix[colIndex]));",
"}"
)
}
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"
)
),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(
iris, editable = list(target = "cell", numeric = "none"),
callback = JS(callback), rownames = FALSE,
options = list(
columnDefs = list(
list(
targets = "_all",
createdCell = JS(createdCell(iris2))
)
)
)
)
}, server = FALSE)
}
shinyApp(ui, server)
EDIT
The previous callback changes the value of the cell only on the display of the table, it does not change the data of the table. It is better to use the following callback:
callback <- c(
"var id = $(table.table().node()).closest('.datatables').attr('id');",
"$.contextMenu({",
" selector: '#' + id + ' td input[type=text]',",
" trigger: 'hover',",
" build: function($trigger, e){",
" var levels = $trigger.parent().data('levels');",
" if(levels === undefined){",
" var colindex = table.cell($trigger.parent()[0]).index().column;",
" levels = table.column(colindex).data().unique();",
" }",
" var options = levels.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;",
" var td = $input.parent();",
" $input.remove();",
" table.cell(td).data(options[data.dropdown]).draw();",
" }",
" }",
" };",
" }",
"});"
)

Update datatable header without refreshing the whole application (R, Shiny) dataTableProxy()

I am trying to make a Shiny app.
(1) I want update data table without refreshing the whole application.
>>> I resolve it with isolate({}) and dataTableProxy().
(2) I want update the header (column name) without refreshing the whole application, for example (If the user choose in the selectInput 2020. The new colname in the table should update to year: 2020 without refreshing the whole application).
How can I do that?
library(DT)
library(data.table)
library(dplyr)
library(shiny)
ui <- fluidPage(
selectInput(inputId = 'choice', label = 'choice', choices = c('A', 'B'), selected = 'A'),
selectInput(inputId = 'country', label = 'country', choices = c('UK', 'USA', 'CANADA'), selected = 'UK'),
selectInput(inputId = 'year', label = 'year', choices = c(2020, 2021), selected = 2020),
uiOutput(outputId = "table")
)
server <- function(input, output, session) {
data_a <- data.frame(year = c(rep(2020,6), rep(2021,6)),
country = c('UK', 'UK', 'USA', 'USA', 'CANADA','CANADA'),
type_a = c('AA', 'BB'),
num = sample(10:22, 12))
dt_a <- reactive({
if (input$choice == 'A'){
return(data_a %>% filter(year == input$year & country == input$country))
}
})
output$rtable_a <- DT::renderDataTable({
if (input$choice == 'A'){
DT <- DT::datatable(isolate({dt_a()}),
rownames = FALSE,
options = list(processing = FALSE),
colnames = c('YEAR', 'COUNTRY', paste('year_a:', input$year), 'NUMBER')
)
return(DT)
}
})
proxy_a <- DT::dataTableProxy('rtable_a')
observe({
DT::replaceData(proxy_a, dt_a(), rownames = FALSE)
})
output$table <- renderUI({
if (input$choice == 'A'){
return(DT::dataTableOutput("rtable_a", width = '75%'))
}
})
}
shinyApp(ui, server)
Here is a solution using the JavaScript library jQuery contextMenu. Right-click on the column header and select the year.
library(shiny)
library(DT)
callback <- c(
"var choices = {1: '2020', 2: '2021'};",
"var id = $(table.table().node()).closest('.datatables').attr('id');",
"$.contextMenu({",
" selector: '#' + id + ' th:eq(3)',",
" trigger: 'right',",
" autoHide: true,",
" items: {",
" select: {",
" name: 'Select year:',",
" type: 'select',",
" options: choices,",
" selected: '1'",
" }",
" },",
" events: {",
" hide: function(opt){",
" var $this = this;",
" var i = $.contextMenu.getInputValues(opt, $this.data()).select;",
" var year = choices[i];",
" Shiny.setInputValue('year', year);",
" var text = 'year_a: ' + year;",
" var $th = opt.$trigger;",
" $th.text(text);",
" }",
" }",
"});"
)
data_a <- data.frame(
"YEAR" = c(rep(2020,6), rep(2021,6)),
"COUNTRY" = c('UK', 'UK', 'USA', 'USA', 'CANADA','CANADA'),
"year_a: 2020" = c('AA', 'BB'),
"NUMBER" = sample(10:22, 12),
check.names = FALSE
)
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.9.2/jquery.contextMenu.min.css"),
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.9.2/jquery.contextMenu.min.js")
),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(data_a, callback = JS(callback))
}, server = FALSE)
observe({ # selected year
print(input[["year"]])
})
}
shinyApp(ui, server)
EDIT
With filtering:
library(shiny)
library(DT)
callback <- c(
"Shiny.setInputValue('year', '2020');",
"var choices = {1: '2020', 2: '2021'};",
"var id = $(table.table().node()).closest('.datatables').attr('id');",
"$.contextMenu({",
" selector: '#' + id + ' th:eq(3)',",
" trigger: 'right',",
" autoHide: true,",
" items: {",
" select: {",
" name: 'Select year:',",
" type: 'select',",
" options: choices,",
" selected: '1'",
" }",
" },",
" events: {",
" hide: function(opt){",
" var $this = this;",
" var i = $.contextMenu.getInputValues(opt, $this.data()).select;",
" var year = choices[i];",
" Shiny.setInputValue('year', year);",
" var text = 'year_a: ' + year;",
" var $th = opt.$trigger;",
" $th.text(text);",
" }",
" }",
"});"
)
data_a <- data.frame(
"YEAR" = c(rep(2020,6), rep(2021,6)),
"COUNTRY" = c('UK', 'UK', 'USA', 'USA', 'CANADA','CANADA'),
"year_a: 2020" = c('AA', 'BB'),
"NUMBER" = sample(10:22, 12),
check.names = FALSE
)
ui <- fluidPage(
tags$head(
tags$link(rel = "stylesheet", href = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.9.2/jquery.contextMenu.min.css"),
tags$script(src = "https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.9.2/jquery.contextMenu.min.js")
),
DTOutput("dtable")
)
server <- function(input, output){
output[["dtable"]] <- renderDT({
datatable(data_a, callback = JS(callback))
}, server = TRUE)
proxy <- dataTableProxy("dtable")
observeEvent(input[["year"]], {
replaceData(proxy, subset(data_a, YEAR == input[["year"]]))
})
}
shinyApp(ui, server)

Shiny - Change column names in DT table for storing as reactiveVal

I have a Shiny app with a DT datatable in which I can change the column names via Javascript (thanks to another Stackoverflow entry). I would like to store the changed column names in a reactiveVal. However, this does currently not work.
Here is the current code which I use:
library(shiny)
library(DT)
callback <- c(
"table.on('dblclick.dt', 'thead th', function(e) {",
" var $th = $(this);",
" var index = $th.index();",
" var colname = $th.text(), newcolname = colname;",
" var $input = $('<input type=\"text\">')",
" $input.val(colname);",
" $th.empty().append($input);",
" $input.on('change', function(){",
" newcolname = $input.val();",
" if(newcolname != colname){",
" $(table.column(index).header()).text(newcolname); ",
" Shiny.onInputChange('newColumnValue', newcolname);",
" console.log( newcolname);",
" }",
" $input.remove();",
" }).on('blur', function(){",
" $(table.column(index).header()).text(newcolname);",
" $input.remove();",
" });",
"});"
)
ui <- fluidPage(
textOutput("value"),
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")
),
DT::dataTableOutput("table")
)
server <- function(input, output){
val <- reactiveVal(NULL)
mydata <- reactive({
browser()
data <- datatable(iris[1:3,], callback = JS(callback))
val(colnames(data$x$data))
data
})
output$table <- DT::renderDataTable({
mydata()
}, server = FALSE)
output$value <- renderText({
val()
})
}
shinyApp(ui, server)
However, I do not get any change in my reactiveVal variable. What do I need to change?
Cheers,
Andi
library(shiny)
library(DT)
callback <- c(
"var colnames = table.columns().header().to$().map(function(){return this.innerHTML;}).get();",
"Shiny.onInputChange('colnames', colnames);",
"table.on('dblclick.dt', 'thead th', function(e) {",
" var $th = $(this);",
" var index = $th.index();",
" var colname = $th.text(), newcolname = colname;",
" var $input = $('<input type=\"text\">')",
" $input.val(colname);",
" $th.empty().append($input);",
" $input.on('change', function(){",
" newcolname = $input.val();",
" if(newcolname != colname){",
" $(table.column(index).header()).text(newcolname);",
" colnames[index] = newcolname;",
" Shiny.onInputChange('colnames', colnames);",
" }",
" $input.remove();",
" }).on('blur', function(){",
" $(table.column(index).header()).text(newcolname);",
" $input.remove();",
" });",
"});"
)
ui <- fluidPage(
verbatimTextOutput("colnames"),
DTOutput("table")
)
server <- function(input, output){
output$table <- renderDT({
datatable(iris[1:3,], callback = JS(callback),
options = list(ordering = FALSE))
}, server = FALSE)
output$colnames <- renderPrint({
input$colnames
})
}
shinyApp(ui, server)

Users were able to download table after edit the headers in DT R Shiny

This is an example the users can edit the headers of column names, I want to add the download button and the users can download the table after edit the header, how I can save the table after they edit. I am appreciated any help
library(shiny)
library(DT)
callback <- c(
"$.contextMenu({",
" selector: '#table th',",
" trigger: 'right',",
" autoHide: true,",
" items: {",
" text: {",
" name: 'Enter column header:',",
" type: 'text',",
" value: ''",
" }",
" },",
" events: {",
" show: function(opts){",
" $.contextMenu.setInputValues(opts, {text: opts.$trigger.text()});",
" },",
" hide: function(opts){",
" var $this = this;",
" var data = $.contextMenu.getInputValues(opts, $this.data());",
" var $th = opts.$trigger;",
" $th.text(data.text);",
" }",
" }",
"});"
)
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"
)
),
DTOutput("table")
)
server <- function(input, output){
output[["table"]] <- renderDT({
datatable(iris, callback = JS(callback))
}, server = FALSE)
}
shinyApp(ui, server)
You just have to use the Buttons extension:
output[["table"]] <- renderDT({
datatable(iris,
callback = JS(callback),
extensions = "Buttons",
options = list(
dom = "Bfrtip",
buttons = list("excel")
)
)
}, server = FALSE)

How to edit column names in datatable function when running R shiny app?

I'm using datatable function from DT package in R Shiny and I want that the user of my app can edit the column names (the variable names). Is there any option to do that?
For now I'm using a text input "old_var_name", a text input "new_var_name" and an actionbutton "update_variable_name". But at this point, I'm only able to change on variable name at the time. I want the user to be able to change as much as variable names he wants.
Server:
tab <- eventReactive(input$import,{
inFile <- input$file1
if (is.null(inFile))
return(NULL)
tabledata <- read.xlsx(inFile$datapath,startRow=1,sheet = 1)
})
name_temp <- eventReactive(input$var_name,{
if (input$old_name == ""){
colnames(tab())
} else {
c(colnames(tab())[1:(which(colnames(tab()) == input$old_name)-1)],input$new_name,
colnames(tab())[(which(colnames(tab()) == input$old_name)+1):length(colnames(tab()))])
}
})
final_rename <- reactive({
d <- tab()
colnames(d) <- name_temp()
d
})
output$tabledata <- DT::renderDataTable({
if (input$var_name == 0) {
DT::datatable(tab(),editable = T)
} else {
DT::datatable(final_rename(),editable = T)
}
})
UI:
tabPanel("Table",h1("Table",align="center") ,
actionButton(inputId = "import", label = "Import data"),br(),br(),
splitLayout(textInput(inputId = "old_name", label = "Old variable name"),
textInput(inputId = "new_name", label = "New variable Name")),
actionButton(inputId = "var_name", label = "Update Variable name"),br(),br(),
DT::dataTableOutput("tabledata"))
Is there any suggestion to achieve that or any option with datatable that I can use and then the user will be able to change all variable names he wants?
Here is a solution with a context menu. Right-click on a column header to edit it. Press 'Escape' when done, or simply move the mouse outside the text input box. This solution does not interfere with the sorting.
library(shiny)
library(DT)
callback <- c(
"$.contextMenu({",
" selector: '#table th',",
" trigger: 'right',",
" autoHide: true,",
" items: {",
" text: {",
" name: 'Enter column header:',",
" type: 'text',",
" value: ''",
" }",
" },",
" events: {",
" show: function(opt){",
" $.contextMenu.setInputValues(opt, {text: opt.$trigger.text()});",
" },",
" hide: function(opt){",
" var $this = this;",
" var text = $.contextMenu.getInputValues(opt, $this.data()).text;",
" var $th = opt.$trigger;",
" $th.text(text);",
" }",
" }",
"});"
)
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")
),
DTOutput("table")
)
server <- function(input, output){
output[["table"]] <- renderDT({
datatable(iris[1:3,], callback = JS(callback))
}, server = FALSE)
}
shinyApp(ui, server)
Here is a friendly way except that it interferes with the sorting. So it is nice only if the sorting is disabled. Double-click on a column header to edit it, and press Tab to escape.
library(DT)
callback <- c(
"table.on('dblclick.dt', 'thead th', function(e) {",
" var $th = $(this);",
" var index = $th.index();",
" var colname = $th.text(), newcolname = colname;",
" var $input = $('<input type=\"text\">')",
" $input.val(colname);",
" $th.empty().append($input);",
" $input.on('change', function(){",
" newcolname = $input.val();",
" if(newcolname != colname){",
" $(table.column(index).header()).text(newcolname);",
" }",
" $input.remove();",
" }).on('blur', function(){",
" $(table.column(index).header()).text(newcolname);",
" $input.remove();",
" });",
"});"
)
datatable(iris[1:3,], callback = JS(callback))

Resources