5

I would like to have an editable DT inside a shiny module. When I change a value in the DT, then the table updates and it is empty with the message inside the datatable:

"No matching records found"

My code is as follows:

Modules:

modDtUi <- function(id){ # UI module
  ns = NS(id)
  DT::dataTableOutput(ns('x1'))
} 


modDt <-  function(input, output, session, data){ # Server module

  x <- data
  output$x1 <- DT::renderDataTable(x, selection = 'none', editable = TRUE)

  proxy <- dataTableProxy('x1', session = session)

  observeEvent(input$x1_cell_edit, {
    info = input$x1_cell_edit
    str(info)
    print(info)
    i = info$row
    j = info$col
    v = info$value
    x[i, j] <<- DT::coerceValue(v, x[i, j])
    replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
  })

}

app in flexdashboard:

```{r}
modDtUi("editable")
```

```{r}
callModule(modDt,"editable", data = iris)
```

It works well without modules, but I can't get the same results with shiny modules.

Thanks

Francesc VE
  • 762
  • 2
  • 9
  • 19
  • 1
    Try passing the module's `session` to `dataTableProxy` – asachet Jun 11 '19 at 09:54
  • 1
    Hi @antoine-sac , I updated the code with: `proxy <- dataTableProxy('x1', session = session)` but it doesn't work. Thanks – Francesc VE Jun 11 '19 at 10:00
  • I don't get what you want to achieve with your observer. The table already updates by itself when edited, so you don't need an observer? – asachet Jun 11 '19 at 10:07
  • It is the example on https://github.com/rstudio/DT/pull/480. Anyway, my intention is to update a database inside the observeEvent or substitute it for eventReactive and use the data in some other place. I can do everything without modules, but the more basic doesn't works with modules. Thanks – Francesc VE Jun 11 '19 at 10:17

3 Answers3

4

This works if you remove rownames = FALSE:

replaceData(proxy, x, resetPaging = FALSE)#, rownames = FALSE)

If you don't want row names, you have to also set rownames = FALSE in the renderDataTable:

  output$x1 <- DT::renderDataTable(x, selection = 'none', editable = TRUE, 
                                   rownames = FALSE)

And then you have to add 1 to info$col:

  observeEvent(input$x1_cell_edit, {
    info = input$x1_cell_edit
    i = info$row
    j = info$col + 1
    v = info$value
    x[i, j] <<- DT::coerceValue(v, x[i, j])
    replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
  })

Full code of the Rmd flexdashboard:

---
title: "Untitled"
output: 
  flexdashboard::flex_dashboard:
    orientation: columns
    vertical_layout: fill
runtime: shiny
---

```{r setup, include=FALSE}
library(flexdashboard)
library(DT)

modDtUi <- function(id){ # UI module
  ns = NS(id)
  DT::dataTableOutput(ns('x1'))
} 

modDt <-  function(input, output, session, data){ # Server module

  x <- data
  output$x1 <- DT::renderDataTable(x, selection = 'none', editable = TRUE, 
                                   rownames = FALSE)

  proxy <- dataTableProxy('x1', session = session)

  observeEvent(input$x1_cell_edit, {
    info = input$x1_cell_edit
    i = info$row
    j = info$col + 1
    v = info$value
    x[i, j] <<- DT::coerceValue(v, x[i, j])
    replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
  })

}
```

Column {data-width=650}
-----------------------------------------------------------------------

### Editable table

```{r}
modDtUi("editable")
```

```{r}
callModule(modDt, "editable", data = iris)
```
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • @antoine-sac I have just edited to add the full code. Does it not work for you ? It works for me. – Stéphane Laurent Jun 11 '19 at 11:01
  • I just copy/pasted your code and I still see the same issue. I can only see the edit for a fraction of second before it is overwritten with the original data. – asachet Jun 11 '19 at 11:04
  • @antoine-sac Strange, I don't get this issue. Works perfectly for me. Are you using the latest versions of the packages ? – Stéphane Laurent Jun 11 '19 at 11:06
2

Working from your code, the issue is that the proxy needs the global session (and not the module session). See my other answer for an alternative approach.

You can simply pass the global session to the module via an argument.

This works:

library(shiny)
library(DT)

modDtUi <- function(id){ # UI module
  ns = NS(id)
  DT::dataTableOutput(ns('x1'))
}


modDt <-  function(input, output, session, data, globalSession){ # Server module

  x <- data
  output$x1 <- DT::renderDataTable(x, selection = 'none', editable = TRUE)

  proxy <- dataTableProxy('x1', session = globalSession)

  observeEvent(input$x1_cell_edit, {
    info = input$x1_cell_edit
    str(info)
    print(info)
    i = info$row
    j = info$col
    v = info$value
    x[i, j] <<- DT::coerceValue(v, x[i, j])
    replaceData(proxy, x, resetPaging = FALSE)
  })

}

You now have to add the global session in the module call.

With a shiny app:

ui <- fluidPage(
  modDtUi("editable")
)

server <- function(input, output, session) {
  callModule(modDt,"editable", data = iris, globalSession = session)
}

shinyApp(ui = ui, server = server)

With a flexdashboard:

```{r}
modDtUi("editable")
```

```{r}
callModule(modDt, "editable", data = iris, globalSession = session)
```

If you want to use your updated table in the rest of your app, simply return reactive(x) from your module and capture it when you call the module.

editable_iris <- callModule(modDt,"editable", data = iris, globalSession = session)
asachet
  • 6,620
  • 2
  • 30
  • 74
0

The following gives me an editable table and captures the edited table in a reactive for further use in the application:

library(shiny)
library(DT)

modDtUi <- function(id){ # UI module
  ns = NS(id)
  DT::dataTableOutput(ns('x1'))
}


modDt <-  function(input, output, session, data){ # Server module

  output$x1 <- DT::renderDataTable(data, selection = 'none', editable = TRUE, server = TRUE)
  proxy <- dataTableProxy('x1', session = session)

  updatedData <- eventReactive(input$x1_cell_edit, {
    info = input$x1_cell_edit
    if (!is.null(info)) {
      str(info)
      data[info$row, info$col] <<- DT::coerceValue(info$value,
                                                   data[info$row, info$col])
    }
    data
  }, ignoreNULL = FALSE)

  return(updatedData)
}

ui <- fluidPage(
  modDtUi("editable"),
  tags$hr(),
  "Proof it works: the table below updates on edit.",
  shiny::tableOutput("proof")
)

server <- function(input, output) {
  editable_dt <- callModule(modDt,"editable", data = iris)

  output$proof <- renderTable({
    editable_dt() %>%
      summarise_if(is.numeric, mean)
  })

}

shinyApp(ui = ui, server = server)

asachet
  • 6,620
  • 2
  • 30
  • 74
  • The [example on github](https://github.com/rstudio/DT/pull/480) (by @Yihui ) says that calling `replaceData` is "important"... But I don't know why! It seems to work without it. Perhaps it only works in this simple case with a static dataset. – asachet Jun 11 '19 at 10:44
  • When we change the `observeEvent` for the `eventReactive` also I see a difference between the code in a module and the code without a module. If I write a `print("hello")` inside the `observeEvent` you can see "hello" in the console when a value is updated. If I do the same with `eventReactive`, when I edit a value in the DT, the print doesn't run. Without modules works in both functions. – Francesc VE Jun 11 '19 at 13:04
  • I believe the `proxy <- dataTableProxy('x1', session = session)` call does nothing – moodymudskipper Feb 22 '22 at 16:22