New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to edit values in table #480

Merged
merged 3 commits into from Jan 18, 2018

Conversation

Projects
None yet
10 participants
@yihui
Member

yihui commented Jan 17, 2018

Closes #28

Test

devtools::install_github('rstudio/DT')

Double click in a table cell to edit its value.

(1) client-side processing (static HTML application)

DT::datatable(iris, editable = TRUE)

(2) client-side processing in Shiny

library(shiny)
library(DT)
shinyApp(
  ui = fluidPage(
    DTOutput('x2')
  ),
  server = function(input, output, session) {
    x = iris
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x2 = renderDT(x, selection = 'none', server = F, editable = T)
  }
)

(3) server-side processing

library(shiny)
library(DT)
shinyApp(
  ui = fluidPage(
    DTOutput('x1')
  ),
  server = function(input, output, session) {
    x = iris
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x1 = renderDT(x, selection = 'none', editable = TRUE)
    
    proxy = dataTableProxy('x1')
    
    observeEvent(input$x1_cell_edit, {
      info = input$x1_cell_edit
      str(info)
      i = info$row
      j = info$col
      v = info$value
      x[i, j] <<- DT::coerceValue(v, x[i, j])
      replaceData(proxy, x, resetPaging = FALSE)  # important
    })
  }
)

(4) server-side processing (without row names)

library(shiny)
library(DT)
shinyApp(
  ui = fluidPage(
    DTOutput('x1')
  ),
  server = function(input, output, session) {
    x = iris
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x1 = renderDT(x, selection = 'none', rownames = F, editable = T)
    
    proxy = dataTableProxy('x1')
    
    observeEvent(input$x1_cell_edit, {
      info = input$x1_cell_edit
      str(info)
      i = info$row
      j = info$col + 1  # column index offset by 1
      v = info$value
      x[i, j] <<- DT::coerceValue(v, x[i, j])
      replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
    })
  }
)

@yihui yihui added this to the v0.3 milestone Jan 17, 2018

yihui added some commits Jan 17, 2018

move up the double click event, to make it possible to edit a pure st…
…atic table on an HTML page without Shiny

@yihui yihui merged commit 5360f0e into master Jan 18, 2018

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
continuous-integration/travis-ci/push The Travis CI build passed
Details

@yihui yihui deleted the feature/editor branch Jan 18, 2018

yihui added a commit that referenced this pull request Jan 18, 2018

@tamluong

This comment has been minimized.

tamluong commented Jan 25, 2018

Hi,

I have a question on this feature. Let's say I have some other analysis that depends on the data in the table. How do link those analysis with the updated data from the edited datatable? For example, if I changed one cell in the table, will the analysis be updated automatically?

I have a example code below (not working)

library(shiny)
library(DT)

ui <- fluidPage(
    DT::dataTableOutput('x1'),
    textOutput("text")
)

server <- function(input, output, session) {
    x = iris
    y <- reactive(x)
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x1 = DT::renderDataTable(x, selection = 'none', editable = TRUE)
    
    proxy = dataTableProxy('x1')

    observeEvent(input$x1_cell_edit, {
        info = input$x1_cell_edit
        str(info)
        i = info$row
        j = info$col
        v = info$value
        x[i, j] <<- DT::coerceValue(v, x[i, j])
        replaceData(proxy, x, resetPaging = FALSE)  # important
    })
    
    output$text <- renderText({
        y()[1, 1]
    })
}

So in this case, I wanted the text output "text" to be automatically updated when I change cell [1,1]. However the code above did not work, what is your thought on this?

Thanks!

@yihui

This comment has been minimized.

Member

yihui commented Jan 25, 2018

@tamluong Your output$text should be dependent on input$x1_cell_edit, otherwise when you edit a cell, it won't be automatically updated. This should work:

    output$text <- renderText({
        input$x1_cell_edit
        x[1, 1]
    })

Note you don't need y(), because x has been updated via <<-.

@tamluong

This comment has been minimized.

tamluong commented Jan 25, 2018

@yihui Thank you! That works perfectly. One thing I noticed is that although y is an reactive expression based on x, y is not automatically updated when x is changed. Was that because Shiny does not recognize x as an input value?

@yihui

This comment has been minimized.

Member

yihui commented Jan 25, 2018

@tamluong That is because although y() was created by reactive(), it is not literally reactive to any inputs. You meant to make it reactive to the edit event, but you didn't state the dependency in its definition. For example, you can associate input$x1_cell_edit with y():

server <- function(input, output, session) {
    x = iris
    y <- reactive({
      input$x1_cell_edit
      x
    })
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x1 = DT::renderDataTable(x, selection = 'none', editable = TRUE)
    
    proxy = dataTableProxy('x1')

    observeEvent(input$x1_cell_edit, {
        info = input$x1_cell_edit
        str(info)
        i = info$row
        j = info$col
        v = info$value
        x[i, j] <<- DT::coerceValue(v, x[i, j])
        replaceData(proxy, x, resetPaging = FALSE)  # important
    })
    
    output$text <- renderText({
        y()[1, 1]
    })
}

Or make output$text reactive to input$x1_cell_edit this way (note y does not need to be reactive):

server <- function(input, output, session) {
    x = iris
    y <- function() x
    x$Date = Sys.time() + seq_len(nrow(x))
    output$x1 = DT::renderDataTable(x, selection = 'none', editable = TRUE)
    
    proxy = dataTableProxy('x1')

    observeEvent(input$x1_cell_edit, {
        info = input$x1_cell_edit
        str(info)
        i = info$row
        j = info$col
        v = info$value
        x[i, j] <<- DT::coerceValue(v, x[i, j])
        replaceData(proxy, x, resetPaging = FALSE)  # important
    })
    
    output$text <- renderText({
        input$x1_cell_edit
        y()[1, 1]
    })
}
@tamluong

This comment has been minimized.

tamluong commented Jan 25, 2018

@yihui that makes perfect sense! Thanks so much!

@KaterinaKou

This comment has been minimized.

KaterinaKou commented May 21, 2018

Hello!
Is there a workaround to make only certain columns editable?

@zackbatist

This comment has been minimized.

zackbatist commented Jun 26, 2018

The coerceValue command is giving me the following error, since according to the docs it only works with integer, double, date, time and factor values:

Warning in DT::coerceValue(v, x[i, j]) :
The data type is not supported: character

The data that i want the user to edit are character values. Any suggestions for a workaround would be immensely appreciated!

@yihui

This comment has been minimized.

Member

yihui commented Jun 26, 2018

@zackbatist #542

devtools::install_github('rstudio/DT')
@adiguzelomer

This comment has been minimized.

adiguzelomer commented Jun 26, 2018

Is there any update on KaterinaKou' s question ?
"
Hello!Is there a workaround to make only certain columns editable?
"
I am looking forward to it too. O.O

@yihui

This comment has been minimized.

Member

yihui commented Jun 26, 2018

@KaterinaKou

This comment has been minimized.

KaterinaKou commented Jun 26, 2018

@adiguzelomer
if you're still looking for a workaround,

fooreactive<-reactiveValues(foovalue={
       #Some kind of dataframe you show on a DT table
}
output$DToutput<-DT::renderDataTable({
        fooreactive[["foovalue"]]
}, editable = TRUE)  #Your DT output
      
proxy = DT::dataTableProxy('DToutput')  #Your DT proxy
      
observeEvent(input$DToutput_cell_edit, {
        info = input$DToutput_cell_edit
        str(info)
        i = info$row
        j = info$col + 1  # column index offset by 1
        v = info$value
        if(j == 3){ #This is the column you want to have as editable
          fooreactive[["foovalue"]]$nameofeditcolumn[i] <- DT::coerceValue(v, fooreactive[["foovalue"]]$nameofeditcolumn[i])
          rep<-fooreactive[["foovalue"]]
          DT::replaceData(proxy, rep, resetPaging = FALSE, rownames = FALSE)
        }
})
@philibe

This comment has been minimized.

philibe commented Jun 27, 2018

Thank you for this functionality @yihui of this merged branch :)

Is there functionality to delete and insert, in the Shiny point of view like editable = TRUE ?

From part (4), I've done this little code for SQL database, (only for update). (SQL Server Windows from ODBC Linux)

library(DT)
library("shiny")
library("RODBCext")

# CREATE  TABLE A_TABLE (
#   id INT ,
#   Col_int INT NULL DEFAULT NULL,
#   col_text VARCHAR(50) NULL DEFAULT NULL,
#   col_date DATETIME NULL DEFAULT NULL,
#   col_numeric NUMERIC(7,4) NULL DEFAULT NULL,
#   CONSTRAINT pk_A_TABLE PRIMARY KEY (id)
# )


updateSQL <- function(table_name,df, pk_name, cell_edited,connexion_bdd ) { 
  r <- FALSE
  
  colnames_list= as.list(colnames(df))
  colname_pk_id=as.character(subset(df, select=c(pk_name ))[cell_edited$row,])
  
  if (is.numeric(cell_edited$value)) {
    sep=""
  } else sep="'"
  
  sql= paste0("UPDATE ",table_name," SET ",  colnames_list[cell_edited$col + 1],
              "=",sep,as.character(cell_edited$value),sep," where ",pk_name,"=",colname_pk_id)
  tryCatch({
     sqlExecute(connexion_bdd, sql,fetch= FALSE, errors = TRUE)
     r <- TRUE
  },error=function(cond) {
    r <- FALSE
  }) 
  return(r)
}

shinyApp(
  ui = fluidPage(
    DTOutput('x1')
  ),
  server = function(input, output, session) {
    
    connexion_bdd <- odbcDriverConnect(connexion_bdd_odbc_txt)  
    
    cancel.onSessionEnded <- session$onSessionEnded(function() {
      odbcClose(connexion_bdd)
    })  

     df <- function(){
       return(as.data.frame(
         sqlQuery( connexion_bdd,  "SELECT  * FROM A_TABLE"   ),
           stringsAsFactors = F
         ))
     }
    
    output$x1 = renderDT(df(), selection = 'none', rownames = F, editable = T)
    proxy = dataTableProxy('x1')    
    
    observeEvent(input$x1_cell_edit, {
      cell_edited = input$x1_cell_edit
      updateSQL ("A_TABLE",df(), 'id', cell_edited,connexion_bdd )  
      replaceData(proxy, df(), resetPaging = FALSE, rownames = FALSE)
    })
    
  }
)
@yihui

This comment has been minimized.

Member

yihui commented Jun 27, 2018

@philibe Currently it is not straightforward, and it may require deeper understanding of the implementation of server-side processing in this package. I did think about wider support for SQL long time ago (see #194), but haven't found time to actually do the work. It will definitely be amazing if all basic SQL operations can be supported in DT (in particular, querying/filtering should be much much faster). Your example is a very good start.

@philibe

This comment has been minimized.

philibe commented Jun 28, 2018

👍 :)

@adiguzelomer

This comment has been minimized.

adiguzelomer commented Jun 28, 2018

@KaterinaKou thank you alot ! I am gonna try it.

@zackbatist

This comment has been minimized.

zackbatist commented Jun 28, 2018

Thanks @yihui, you're of great help. I also agree with @philibe, such SQL functionality would be extremely useful!

@ddelarosa70

This comment has been minimized.

ddelarosa70 commented Jul 23, 2018

is there a way to use the all the extensions that DT have a long with the editing. ie. fixed columns, hiding columns etc.

As far as I can tell and test there is only
output$x <- DT::renderDT( data, selection = 'none', server = F, editable = T )

if I try to use an expression to calculate the data and return datatable with the extensions, the editing is ignored , and viceversa

@stamatelou

This comment has been minimized.

stamatelou commented Sep 14, 2018

Hi! Is there any way that editable= TRUE for specific columns and not for the whole datatable?

@adityaraj04

This comment has been minimized.

adityaraj04 commented Oct 9, 2018

how to write/save the file after making changes to it?

@Bidossessih

This comment has been minimized.

Bidossessih commented Oct 22, 2018

Hi @yihui ,
I would like to know how to enable the value edition in small device browser. I tried your example (https://yihui.shinyapps.io/DT-edit/) in mobile phone but I can't edit the table. It works perfectly when accessing from my computer. I need this functionality in an app optimized for small device. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment