Skip to content
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

Select rows using checkboxes #93

Closed
becarioprecario opened this issue Jun 3, 2015 · 16 comments
Closed

Select rows using checkboxes #93

becarioprecario opened this issue Jun 3, 2015 · 16 comments

Comments

@becarioprecario
Copy link

Hi,

I have been using DT to show a table where columns could be selected using checkboxes (in the row names) and the column ids saved into an .RData file. However, after updating shiny and DT, this feature seems not to be working. Also, the page where I took the example from (https://yihui.shinyapps.io/DT-checkbox/) seems to have disappeared, so I presume that this has something to do with the latest release of shiny, DT and/or DataTable. Is there an easy fix to this?

I am using the latest version of shiny and DT from github.

Best,

Virgilio

@pssguy
Copy link

pssguy commented Jun 3, 2015

This might be relevant
708bd06#commitcomment-11500032

@becarioprecario
Copy link
Author

Hi,

Many thanks. This is certainly relevant. My question now is how to show the table with a certain number of rows selected by default. This was very easy to do with checkboxes but I am not sure how to do it now.

Thanks!

Virgilio

@maxmoro
Copy link

maxmoro commented Jun 4, 2015

I added also a note here: #89
I think adding a vector to the parameters containing the list of rows ID or row selected would help

@becarioprecario
Copy link
Author

Hi,

You can use something like

$("#mytable tbody tr")[0].click()

in Javascript to click on an initial set of rows. The problem is that I do not know how to run the JS code on startup only...

V

@ajholguin
Copy link

You should be able to use the initComplete option in DataTables to call the javascript function, but it doesn't look like it works for rows beyond the first page:

library(shiny)
library(DT)

jsfunc <- "function() {arrIndexes=[1,3,4,8,12]; $('#tbl tbody tr').filter(function(index) {return arrIndexes.indexOf(index) > -1;}).click()}"

shinyApp(
  ui = fluidPage(dataTableOutput('tbl')),
  server = function(input, output) {
    output$tbl = renderDataTable(
      datatable(iris, selection = "multiple", options = list(initComplete = JS(jsfunc)))
    )
  }
)

@becarioprecario
Copy link
Author

Hi,

Many thanks for the hint. This is really useful and I have been able to update my code to the latest version! :)

Regarding your comment about it only working in the first page, you may look at how select rows using server-side selection:

https://yihui.shinyapps.io/DT-rows/

I am sure that you can find a way of defining an index that will span the whole table and not the current page.

Many thanks!

Virgilio

@stanstrup
Copy link

I tried to merge the example from @ajholguin with https://yihui.shinyapps.io/DT-rows/ with no luck.

List of issues:

  • If I use server = TRUE I get unused argument (server = TRUE) also with the unmodified example code. I am running github versions. Is there some version incompatibility? Seems this was just updated? With server being an argument to renderDataTable instead of datatable. Updated my code.
  • Only the first page works with the jsfunc trick. Manual selection works.
  • Indexes set with jsfunc is one unit off. Well, easy to fix. more like a note.

Any ideas?

library(shiny)
library(DT)

jsfunc <- "function() {arrIndexes=[1,3,4,8,12]; $('#tbl tbody tr').filter(function(index) {return arrIndexes.indexOf(index) > -1;}).click()}"

shinyApp(


  ui = fluidPage(DT::dataTableOutput('tbl'),verbatimTextOutput('x4')),




  server = function(input, output,session) {

    iris2 <- iris
    rownames(iris2) <- paste0("row",rownames(iris))

        output$tbl = DT::renderDataTable(iris2, rownames = TRUE, server=TRUE,selection = "multiple", options = list(initComplete = JS(jsfunc))   )


    output$x4 = renderPrint({
      s = input$tbl_rows_selected
      if (length(s)) {
        cat('These rows were selected:\n\n')
        cat(s, sep = '\n')
      }
    })

  }


)

@yihui
Copy link
Member

yihui commented Jun 11, 2015

So there are actually two issues blended into this discussion. Let's keep the discussion of pre-selected rows in #89 only. For checkboxes, I just prepared an example in the shiny-discuss mailing list: https://groups.google.com/forum/#!topic/shiny-discuss/FeqU0AoTpz0

library(shiny)
library(DT)
shinyApp(
  ui = fluidPage(DT::dataTableOutput('x1'), verbatimTextOutput('x2')),

  server = function(input, output) {
    # create a character vector of shiny inputs
    shinyInput = function(FUN, len, id, ...) {
      inputs = character(len)
      for (i in seq_len(len)) {
        inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
      }
      inputs
    }

    # obtain the values of inputs
    shinyValue = function(id, len) {
      unlist(lapply(seq_len(len), function(i) {
        value = input[[paste0(id, i)]]
        if (is.null(value)) NA else value
      }))
    }

    # a sample data frame
    res = data.frame(
      v1 = shinyInput(numericInput, 100, 'v1_', value = 0),
      v2 = shinyInput(checkboxInput, 100, 'v2_', value = TRUE),
      v3 = rnorm(100),
      v4 = sample(LETTERS, 100, TRUE),
      stringsAsFactors = FALSE
    )

    # render the table containing shiny inputs
    output$x1 = DT::renderDataTable(
      res, server = FALSE, escape = FALSE, selection = 'none', options = list(
        preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
        drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
      )
    )
    # print the values of inputs
    output$x2 = renderPrint({
      data.frame(v1 = shinyValue('v1_', 100), v2 = shinyValue('v2_', 100))
    })
  }
)

This is a more general solution than checkboxes -- you can use arbitrary shiny inputs in the table, e.g. numericInput, textInput, checkboxInput, and so on.

@SamoPP
Copy link

SamoPP commented Jul 16, 2015

I am trying to implement deletion of selected (using checkboxes) rows but strange things are happening. Any hint on how to implement deletion of selrcted rows highly appreciated. Please run the code below to see the problem (select just a few rows using checkboxes). When the app shows in browser select first 7 rows. Click "Delete Selected" button. Then you are left with three rows and all three checkboxes are not checked. Then if you press "Delete Selected" button again all three rows (although unchecked) are deleted. I tried to use updateCheckboxInput() but without success.

# need the latest version of shiny due to https://github.com/rstudio/shiny/pull/857 
if (packageVersion('shiny') < '0.12.1') {
    update.packages(ask=FALSE, repos='http://cran.rstudio.com')
}

library(shiny)
library(DT)

howManyRows <- 10

shinyApp(
        ui <- fluidPage( 
                DT::dataTableOutput('x1'), 
                actionButton("deleteSelected", "Delete Selected"),
                br(),
                verbatimTextOutput('x2')),

        server<-function(input, output, session) {
            # create a character vector of shiny inputs
            shinyInput<-function(FUN, len, id, ...) {
                inputs <- character(len)
                for (i in seq_len(len)) {
                    inputs[i] <- as.character(FUN(paste0(id, i), label=NULL, ...))
                }
                inputs
            }

            # obtain the values of inputs
            shinyValue<-function(id, len) {
                unlist(lapply(seq_len(len), function(i) {
                                    value <- input[[paste0(id, i)]]
                                    if (is.null(value)) NA else value
                                }))
            }           

            # a sample data frame
            values <- reactiveValues(res=data.frame(
                    v1=shinyInput(numericInput, howManyRows, 'v1_', value=0),
                    v2=shinyInput(checkboxInput, howManyRows, 'v2_', value=FALSE),
                    v3=rnorm(howManyRows),
                    v4=sample(LETTERS, howManyRows, TRUE),
                    stringsAsFactors=FALSE)
            )

            # render the table containing shiny inputs
            output$x1 <- DT::renderDataTable(
                    values$res, server=FALSE, escape=FALSE, options=list(
                            preDrawCallback=JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
                            drawCallback=JS('function() { Shiny.bindAll(this.api().table().node()); } ')
                    )
            )

            observeEvent(input$deleteSelected, {
                        print(NROW(values$res))
                        selectedRows <- shinyValue("v2_", NROW(values$res))

                        print(selectedRows)

                        print(paste0("Deleting selectedRows=", paste(selectedRows, collapse=", ")))

                        newTable <- values$res[-which(selectedRows), ]

                        howManyRows <<- NROW(newTable)

                        values$res <- newTable
                    })          


            # print the values of inputs
            output$x2 <- renderPrint({
                        input$deleteSelected

                        data.frame(v1 = shinyValue('v1_', howManyRows), v2 = shinyValue('v2_', howManyRows))
                    })
        }
)

@yihui
Copy link
Member

yihui commented Jul 19, 2015

Selecting rows and pre-selecting rows are both possible with the current development version now, so I'm closing this issue. Please check out the documentation http://rstudio.github.io/DT/shiny.html and example https://yihui.shinyapps.io/DT-selection You are welcome to open a new issue if you still think checkboxes are better than the current way of row selection.

@yihui yihui closed this as completed Jul 19, 2015
@SamoPP
Copy link

SamoPP commented Jul 20, 2015

Hi,

Thanks for your answer. I have a use case when I need both, check boxes and
"regular" row selection in DT. So I will open a new issue. Thanks for
considering it.

Best regards,
Samo

2015-07-19 0:23 GMT-04:00 Yihui Xie notifications@github.com:

Closed #93 #93.


Reply to this email directly or view it on GitHub
#93 (comment).

@thecomeonman
Copy link

thecomeonman commented Dec 5, 2016

I'm trying to reuse @yihui's example except I need to be able to change the underlying datatable itself. I think the reactive link gets broken when I reuse the inputIds the second time. I don't understand the code in the JS() but I thought the unbindAll probably took care of that.

I also tried running an explicit removeUI in the renderTable chunk but that didn't work either.

What can I do?

App example below -

library(shiny)
library(DT)
shinyApp(
  ui = fluidPage(numericInput(inputId = 'n', value = 100, label = 'No. of rows'), DT::dataTableOutput('x1'), verbatimTextOutput('x2')),

  server = function(input, output) {
    # create a character vector of shiny inputs
    shinyInput = function(FUN, len, id, ...) {
      inputs = character(len)
      for (i in seq_len(len)) {
        inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
      }
      inputs
    }

    # obtain the values of inputs
    shinyValue = function(id, len) {
      unlist(lapply(seq_len(len), function(i) {
        value = input[[paste0(id, i)]]
        if (is.null(value)) NA else value
      }))
    }

    # a sample data frame
    res = reactive({
       
       data.frame(
         v1 = shinyInput(numericInput, input$n, 'v1_', value = 0),
         v2 = shinyInput(checkboxInput, input$n, 'v2_', value = TRUE),
         v3 = rnorm(input$n),
         v4 = sample(LETTERS, input$n, TRUE),
         stringsAsFactors = FALSE
       )
  })

    # render the table containing shiny inputs
    output$x1 = DT::renderDataTable(
      res(), server = FALSE, escape = FALSE, selection = 'none', options = list(
        preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
        drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
      )
    )
    # print the values of inputs
    output$x2 = renderPrint({
      data.frame(v1 = shinyValue('v1_', input$n), v2 = shinyValue('v2_', input$n))
    })
  }
)

@carlganz
Copy link
Contributor

carlganz commented Dec 5, 2016

See this SO answer: http://stackoverflow.com/questions/40020600/r-shiny-selectedinput-inside-renderdatatable-cells/40027726#40027726

Per @yihui here: https://groups.google.com/forum/#!msg/shiny-discuss/ZUMBGGl1sss/7sdRQecLBAAJ

When you rerender the table you need to add an extra function to unbind. It will generate a javascript error because it will try to unbind on load before the table exists, but it's a benign error me thinks.

@phineas-pta
Copy link

hello, i'm using the 2 functions of @yihui for my app, but i'm having problem with updateCheckboxInput when enabling Buttons and FixedColumns extensions along with scrollX, scrollY and fixedColumns options.

the updateCheckboxInput works as expected (change values) but in the UI the checkbox isn't updated

app example below:

library(shiny)
library(DT)

# create a character vector of shiny inputs
shinyInput <- function(FUN, len, id, ...) {
	inputs <- character(len)
	for (i in seq_len(len))
		inputs[i] <- as.character(FUN(paste0(id, i), label = NULL, ...))
	inputs
}

# obtain the values of inputs
shinyValue <- function(input, id, len)
	unlist(lapply(seq_len(len), function(i) {
		value <- input[[paste0(id, i)]]
		if (is.null(value)) NA else value
	}))

shinyApp(
	ui = fluidPage(
		numericInput(inputId = "n", value = 20, label = "No. of rows"),
		actionButton(inputId = "unselectAll", label = "Unselect all", icon = icon("minus")),
		DT::dataTableOutput("x1"),
		verbatimTextOutput("x2")
	),

	server = function(input, output, session) {

		# a sample data frame
		res = reactive({

			data.frame(
				v2 = shinyInput(checkboxInput, input$n, "v2_", value = TRUE),
				v3 = rnorm(input$n),
				v4 = sample(LETTERS, input$n, TRUE),
				stringsAsFactors = FALSE
			)
		})

		# render the table containing shiny inputs
		output$x1 = DT::renderDataTable(res(),
			selection = "none", rownames = FALSE, escape = FALSE,
			extensions = c("Buttons", "FixedColumns"),
			options = list(
				preDrawCallback = JS("function() {Shiny.unbindAll(this.api().table().node());}"),
				drawCallback = JS("function() {Shiny.bindAll(this.api().table().node());}"),
				scrollX = TRUE, scrollY = "500px",
				dom = "Bfrtip",
				fixedColumns = list(leftColumns = 1)
			)
		)

		# button select all
		observeEvent(input$unselectAll, {
			for (i in input$x1_rows_all)
				updateCheckboxInput(session, inputId = paste0("v2_", i), value = FALSE)
		})

		# print the values of inputs
		output$x2 = renderPrint({
			data.frame(v2 = shinyValue(input, "v2_", input$n))
		})
	}
)

@ElenJ
Copy link

ElenJ commented Mar 11, 2022

I encounter a strange behavior, when I try to combine these checkBoxes in DT table with editable cells. I have built upon yihui's code and try to make the Letters column editable. However, when I check some boxes in the first column, make edits in the second column (the letters) and then click somewhere, the boxes are all unchecked again. I suppose it's due to the proxy refreshing, but how can I overcome this?
This is my code:

`
library(shiny)
library(DT)

shinyApp(
ui = fluidPage(DT::dataTableOutput('x1')),

server = function(input, output) {
    # create a character vector of shiny inputs
    shinyInput = function(FUN, len, id, ...) {
        inputs = character(len)
        for (i in seq_len(len)) {
            inputs[i] = as.character(FUN(paste0(id, i), label = NULL,  width = "20px", ...))
        }
        inputs
    }
    
    # obtain the values of inputs
    shinyValue = function(id, len) {
        unlist(lapply(seq_len(len), function(i) {
            value = input[[paste0(id, i)]]
            if (is.null(value)) NA else value
        }))
    }
    
    # default global search value
    if (!exists("default_search")) default_search <- ""
    
    # default column search values
    if (!exists("default_search_columns")) default_search_columns <- NULL
    
    # a sample data frame
    res = data.frame(
        v2 = shinyInput(checkboxInput, 10, 'v2_', value = FALSE),
        v4 = sample(LETTERS, 10, TRUE),
        stringsAsFactors = FALSE
    )
    
    #Container to store edits
    reactive_df = reactiveValues(df = NULL)
    
    observe({
        reactive_df$edited_df <- res
    })
    
    # render the table containing shiny inputs
    output$x1 = DT::renderDataTable(
        res
        , server = TRUE
        , escape = FALSE
        , filter = 'top'
        , selection = list(mode="single", target="cell")
        , editable = list(target = "cell", disable = list(columns = 1))
        , options = list(
            scrollX = TRUE
            # , paging = TRUE
            # ,searching = TRUE
            ,fixedColumns = TRUE
            ,autoWidth = TRUE
            ,columnDefs = list(list(targets = c(1), searchable = FALSE)) #make the checkbox columns unsearchable
            ,ordering = TRUE
            # # default column search strings and global search string
            ,searchCols = default_search_columns
            ,search = list(regex = FALSE, caseInsensitive = FALSE, search = default_search)
            ,preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }')
            ,drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
        )
    )
    # make a proxy of the data table so it can be edited after it's been rendered
    proxy = DT::dataTableProxy("x1")
    
    observeEvent(input$x1_cell_edit, {
        info = input$x1_cell_edit
        reactive_df$edited_df <<- editData(reactive_df$edited_df, info)
        replaceData(proxy, reactive_df$edited_df, resetPaging = FALSE)  
    })

}

)
`

EDIT: I found my mistake. Changing the code to:
observeEvent(input$x1_cell_edit, { info = input$x1_cell_edit reactive_df$edited_df <<- editData(reactive_df$edited_df, info) })

solved the issue. I leave the code snippet, in case someone finds it helpful.

@stla
Copy link
Collaborator

stla commented Mar 11, 2022

You can use the checkboxes of the Select extension. But there's something strange: one sees the row names. One didn't see them before.

library(DT)

dat <- iris

datatable(
  dat, #rownames = FALSE,
  extensions = "Select", selection = "none", 
  options = list(
    columnDefs = list(
      list(targets = 0, orderable = FALSE, className = "select-checkbox")
    ),
    select = list(
      style = "multi", selector = "td:first-child"
    )
  )
)

DTcheckboxes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests