gpt4 book ai didi

javascript - 如何从 Shiny 的 DT 中的单选按钮(使用 JS 回调制作)访问用户输入,并在一个 DT 中有不同的 JS 元素?

转载 作者:行者123 更新时间:2023-12-03 23:40:07 26 4
gpt4 key购买 nike

我正在尝试将两个常用共享的 JS 回调组合到一个 R 数据表 Shiny 应用程序(具有单选按钮(参见 https://yihui.shinyapps.io/DT-radio/Extracting user input values from radio buttons in Shiny DT into a dataframe or list)并在子/父表中嵌套行(参见 https://stackoverflow.com/a/56599838/10624798 和许多其他地方) . 单独他们都可以工作,但不能一起工作。我不确定我是否错误地绑定(bind)了 JS 或者 JS 以某种方式相互矛盾?无论哪种方式,我都无法再访问用户输入(如果我看的话,只会返回一个 null在输入 $xxx 的结构中。我已经包含了一个小例子(虽然仍然很长)我的意思。

library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)


shinyApp(
ui = fluidPage(
title = 'Radio button and a dropdown manue ',
sliderInput("n_rows_table", "Number of rows:",
min = 0, max = 10,
value = 5),
actionBttn(
inputId = "btnCancel",
label = "Make tables",
size = "sm",
color = "warning"
),
p("THIS EXAMPLE DOES NOT WORK!"),
DT::dataTableOutput("datatable"),
verbatimTextOutput('sel'),
p("THIS SIMPLER EXAMPLE DOES WORK!"),
DT::dataTableOutput("datatable2"),
verbatimTextOutput('sel2'),
p("These of the R6 input class objects, the the ones from the first tabel do not show up"),

verbatimTextOutput('sel_all'),

),

server = function(input, output, session) {


# Ideally instead of working with a counter,
# this would just override the old value so instead of a_1, a_2,
# everything you click the button it just sets input$a back to null
# until the users clicks again.
# But in the meantime this is a work around

counter <- reactiveValues(countervalue = 0) # Defining & initializing the reactiveValues object

observeEvent(input$btnCancel, {
counter$countervalue <- counter$countervalue + 1 # if the add button is clicked, increment the value by 1 and update it
})


# ----- Create a table based on the number of rows from the slider
# ----- and create it when the user clicks the button
data_for_table <- eventReactive(
input$btnCancel,
{
tibble(
let_rowid = paste0(letters[1:input$n_rows_table], "_", counter$countervalue ),
val_1 = round(runif(input$n_rows_table, 0, 10), 1),
val_2 = round(rnorm(input$n_rows_table), 2),
val_3 = round(rnorm(input$n_rows_table), 2),
val_4 = letters[1:input$n_rows_table],
Yes = "Yes",
No = "No",
Maybe = "Maybe",
result = NA # ideally the what ever selection in yes/no/maybe shows up in this column (future improvement)
) %>%
mutate(oplus = "&oplus;") %>%
relocate(oplus) %>%
mutate(
Yes = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , Yes),
No = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , No),
Maybe = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , Maybe)
) %>%
nest(datalist = c(val_3, val_4)) %>%
mutate(datalist = map(datalist, as.list)) %>%
mutate(datalist = map(datalist, list))

})

# ----- Render the table
# ----- The table renders ok.
output$datatable <- DT::renderDT({
parentRows <- which(data_for_table()[,1] != "")

# ------ This JS is neede to make the child/parent dropdown
callback <- JS(
sprintf("var parentRows = [%s];", toString(parentRows-1)),
sprintf("var j0 = %d;", 0),
"var nrows = table.rows().count();",
"for(let i = 0; i < nrows; ++i){",
" var $cell = table.cell(i,j0).nodes().to$();",
" if(parentRows.indexOf(i) > -1){",
" $cell.css({cursor: 'pointer'});",
" }else{",
" $cell.removeClass('details-control');",
" }",
"}",
"",
"// --- make the table header of the nested table --- //",
"var formatHeader = function(d, childId){",
" if(d !== null){",
" var html = ",
" '<table class=\"display compact hover\" ' + ",
" 'style=\"padding-left: 30px;\" id=\"' + childId + ",
" '\"><thead><tr>';",
" var data = d[d.length-1] || d.datalist;",
" for(let key in data[0]){",
" html += '<th>' + key + '</th>';",
" }",
" html += '</tr></thead></table>'",
" return html;",
" } else {",
" return '';",
" }",
"};",
"",
"// --- row callback to style rows of child tables --- //",
"var rowCallback = function(row, dat, displayNum, index){",
" if($(row).hasClass('odd')){",
" $(row).css('background-color', 'papayawhip');",
" $(row).hover(function(){",
" $(this).css('background-color', '#E6FF99');",
" }, function(){",
" $(this).css('background-color', 'papayawhip');",
" });",
" } else {",
" $(row).css('background-color', 'lemonchiffon');",
" $(row).hover(function(){",
" $(this).css('background-color', '#DDFF75');",
" }, function(){",
" $(this).css('background-color', 'lemonchiffon');",
" });",
" }",
"};",
"",
"// --- header callback to style header of child tables --- //",
"var headerCallback = function(thead, data, start, end, display){",
" $('th', thead).css({",
" 'border-top': '3px solid indigo',",
" 'color': 'indigo',",
" 'background-color': '#fadadd'",
" });",
"};",
"",
"// --- make the datatable --- //",
"var formatDatatable = function(d, childId){",
" var data = d[d.length-1] || d.datalist;",
" var colNames = Object.keys(data[0]);",
" var columns = colNames.map(function(x){",
" return {data: x.replace(/\\./g, '\\\\\\.'), title: x};",
" });",
" var id = 'table#' + childId;",
" if(colNames.indexOf('datalist') === -1){",
" var subtable = $(id).DataTable({",
" 'data': data,",
" 'columns': columns,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': data.length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [{targets: '_all', className: 'dt-center'}]",
" });",
" } else {",
" var subtable = $(id).DataTable({",
" 'data': data,",
" 'columns': columns,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': data.length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [",
" {targets: -1, visible: false},",
" {targets: 0, orderable: false, className: 'details-control'},",
" {targets: '_all', className: 'dt-center'}",
" ]",
" }).column(0).nodes().to$().css({cursor: 'pointer'});",
" }",
"};",
"",
"// --- display the child table on click --- //",
"// array to store id's of already created child tables",
"var children = [];",
"table.on('click', 'td.details-control', function(){",
" var tbl = $(this).closest('table'),",
" tblId = tbl.attr('id'),",
" td = $(this),",
" row = $(tbl).DataTable().row(td.closest('tr')),",
" rowIdx = row.index();",
" if(row.child.isShown()){",
" row.child.hide();",
" td.html('&oplus;');",
" } else {",
" var childId = tblId + '-child-' + rowIdx;",
" if(children.indexOf(childId) === -1){",
" // this child has not been created yet",
" children.push(childId);",
" row.child(formatHeader(row.data(), childId)).show();",
" td.html('&CircleMinus;');",
" formatDatatable(row.data(), childId, rowIdx);",
" }else{",
" // this child has already been created",
" row.child(true);",
" td.html('&CircleMinus;');",
" }",
" }",
"}); ",
"// --- add radio button functionality --- //",
"table.rows().every(function(i, tab, row) {",
" var $this = $(this.node());",
" $this.attr('id', this.data()[0]);",
" $this.addClass('shiny-input-radiogroup');",
" });",
" Shiny.unbindAll(table.table().node());",
" Shiny.bindAll(table.table().node());")


datatable(
data_for_table(),
escape = F,
rownames = F,
callback = callback,
options = list(
dom = 't',
paging = FALSE,
ordering = FALSE,
paging = FALSE,
searching = FALSE,
columnDefs = list(
list(
visible = FALSE,
targets = c(c(1, ncol(data_for_table())-1)) # do not show certain ID variables, we do not need
),
list(
orderable = FALSE,
className = "details-control",
targets = 0
),
list(
className = "dt-left",
targets = "_all"
)
)
)

)
},
server = F)


list_results <- reactive({
list_values <- list()
for (i in unique(data_for_table()$let_rowid)) {
list_values[[i]] <- paste0(i, ": ", input[[i]])

}
list_values
})

output$sel = renderPrint({
list_results()
})




####################################
## this simpler version does work ##
####################################

data_for_table2 <- eventReactive(
input$btnCancel,
{
tibble(
let_rowid = paste0(letters[11:(10+input$n_rows_table)], "_", counter$countervalue ),
val_1 = round(runif(input$n_rows_table, 0, 10), 1),
val_2 = round(rnorm(input$n_rows_table), 2),
val_3 = round(rnorm(input$n_rows_table), 2),
val_4 = letters[1:input$n_rows_table],
Yes = "Yes",
No = "No",
Maybe = "Maybe",
result = NA # ideally the what ever selection in yes/no/maybe shows up in this column (future improvement)
) %>%
mutate(
Yes = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , Yes),
No = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , No),
Maybe = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , Maybe)
) })


output$datatable2 <- DT::renderDT({

# ---- only difference here is he lack of a drop down.
callback <- JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-radiogroup');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());")


datatable(
data_for_table2(),
escape = F,
rownames = F,
callback = callback,
options = list(
dom = 't',
paging = FALSE,
ordering = FALSE,
paging = FALSE,
searching = FALSE,
columnDefs = list(
# list(
# visible = FALSE,
# targets = c(ncol(data_for_table2())-1+0) # do not show certain ID variables, we do not need
# ),
list(
orderable = FALSE,
className = "details-control",
targets = 0
),
list(
className = "dt-left",
targets = "_all"
)
)
)

)
},
server = F)

list_results2 <- reactive({
list_values <- list()
for (i in unique(data_for_table2()$let_rowid)) {
list_values[[i]] <- paste0(i, ": ", input[[i]])

}
list_values
})

output$sel2 = renderPrint({
list_results2()
})


# make this regex working
list_results_all <- reactive({
list_values_all <- list()
for(i in names(input)[grepl("([a-z]{1}_)([0-9]{1,3})",names(input))]){
list_values_all[[i]] <- tibble(id = i, value = paste0(input[[i]]))

}
do.call(rbind, list_values_all)

})


output$sel_all = renderPrint({
list_results_all()
})

}
)
编辑(附加问题)
给出的答案解决了我对 MWE 的原始问题。但是,深入研究它并通过在表格中添加额外类型的按钮,单选按钮仍然会中断(例如,通过添加删除按钮,如果我首先单击单选按钮然后删除它确实有效,但不是反之亦然)。有没有办法让这种行为更加一致?
(部分代码取自这个问题的答案: R Shiny: Remove Row Button in Data Table)
library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)


# 1) These two function allows for setting a remove function in the app.
# This code is taken from here: https://stackoverflow.com/questions/53908266/r-shiny-remove-row-button-in-data-table

getRemoveButton <- function(n, idS = "", lab = "Pit") {
if (stringr::str_length(idS) > 0) idS <- paste0(idS, "-")
ret <- shinyInput(actionButton, n,
'button_', label = "Remove",
onclick = sprintf('Shiny.onInputChange(\"%sremove_button_%s\", this.id)' ,idS, lab))
return (ret)
}

shinyInput <- function(FUN, n, id, ses, ...) {
as.character(FUN(paste0(id, n), ...))
}


shinyApp(
ui = fluidPage(
title = 'Radio button and a dropdown manue ',
sliderInput("n_rows_table", "Number of rows:",
min = 0, max = 10,
value = 5),
actionBttn(
inputId = "btnCancel",
label = "Make tables",
size = "sm",
color = "warning"
),
p("THIS EXAMPLE DOES NOT WORK!"),
DT::dataTableOutput("datatable"),
verbatimTextOutput('sel'),
#p("THIS SIMPLER EXAMPLE DOES WORK!"),
#DT::dataTableOutput("datatable2"),
#verbatimTextOutput('sel2'),
#("These of the R6 input class objects, the the ones from the first tabel do not show up"),
#verbatimTextOutput('sel_all'),

),

server = function(input, output, session) {


# Ideally instead of working with a counter,
# this would just override the old value so instead of a_1, a_2,
# everything you click the button it just sets input$a back to null
# until the users clicks again.
# But in the meantime this is a work around

counter <- reactiveValues(countervalue = 0) # Defining & initializing the reactiveValues object

observeEvent(input$btnCancel, {
counter$countervalue <- counter$countervalue + 1 # if the add button is clicked, increment the value by 1 and update it
})




values <- reactiveValues(tab = NULL)


# ----- Create a table based on the number of rows from the slider
# ----- and create it when the user clicks the button
observeEvent(
input$btnCancel,
{
values$tab <- tibble(
let_rowid = paste0(letters[1:input$n_rows_table], "_", counter$countervalue ),
val_1 = round(runif(input$n_rows_table, 0, 10), 1),
val_2 = round(rnorm(input$n_rows_table), 2),
val_3 = round(rnorm(input$n_rows_table), 2),
val_4 = letters[1:input$n_rows_table],
Yes = "Yes",
No = "No",
Maybe = "Maybe",
result = NA # ideally the what ever selection in yes/no/maybe shows up in this column (future improvement)
) %>%
mutate(oplus = "&oplus;") %>%
relocate(oplus) %>%
mutate(
Yes = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , Yes),
No = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , No),
Maybe = sprintf('<input type="radio" name="%s" value="%s"/>', let_rowid , Maybe)
) %>%
## THIS IS NEW ###################################################
mutate(id = 1:n()) %>% #
rowwise() %>% #
mutate(Remove = getRemoveButton(id, idS = "", lab = "Tab1"))%>% #
ungroup() %>% #
##################################################################
nest(datalist = c(val_3, val_4)) %>%
mutate(datalist = map(datalist, as.list)) %>%
mutate(datalist = map(datalist, list))

})

# add a proxy table
proxyTable <- DT::dataTableProxy("tab")


# ----- Render the table
# ----- The table renders ok.
output$datatable <- DT::renderDT({
parentRows <- which(values$tab[,1] != "")

# ------ This JS is neede to make the child/parent dropdown
callback <- JS(
sprintf("var parentRows = [%s];", toString(parentRows-1)),
sprintf("var j0 = %d;", 0),
"var nrows = table.rows().count();",
"for(let i = 0; i < nrows; ++i){",
" var $cell = table.cell(i,j0).nodes().to$();",
" if(parentRows.indexOf(i) > -1){",
" $cell.css({cursor: 'pointer'});",
" }else{",
" $cell.removeClass('details-control');",
" }",
"}",
"",
"// --- make the table header of the nested table --- //",
"var formatHeader = function(d, childId){",
" if(d !== null){",
" var html = ",
" '<table class=\"display compact hover\" ' + ",
" 'style=\"padding-left: 30px;\" id=\"' + childId + ",
" '\"><thead><tr>';",
" var data = d[d.length-1] || d.datalist;",
" for(let key in data[0]){",
" html += '<th>' + key + '</th>';",
" }",
" html += '</tr></thead></table>'",
" return html;",
" } else {",
" return '';",
" }",
"};",
"",
"// --- row callback to style rows of child tables --- //",
"var rowCallback = function(row, dat, displayNum, index){",
" if($(row).hasClass('odd')){",
" $(row).css('background-color', 'papayawhip');",
" $(row).hover(function(){",
" $(this).css('background-color', '#E6FF99');",
" }, function(){",
" $(this).css('background-color', 'papayawhip');",
" });",
" } else {",
" $(row).css('background-color', 'lemonchiffon');",
" $(row).hover(function(){",
" $(this).css('background-color', '#DDFF75');",
" }, function(){",
" $(this).css('background-color', 'lemonchiffon');",
" });",
" }",
"};",
"",
"// --- header callback to style header of child tables --- //",
"var headerCallback = function(thead, data, start, end, display){",
" $('th', thead).css({",
" 'border-top': '3px solid indigo',",
" 'color': 'indigo',",
" 'background-color': '#fadadd'",
" });",
"};",
"",
"// --- make the datatable --- //",
"var formatDatatable = function(d, childId){",
" var data = d[d.length-1] || d.datalist;",
" var colNames = Object.keys(data[0]);",
" var columns = colNames.map(function(x){",
" return {data: x.replace(/\\./g, '\\\\\\.'), title: x};",
" });",
" var id = 'table#' + childId;",
" if(colNames.indexOf('datalist') === -1){",
" var subtable = $(id).DataTable({",
" 'data': data,",
" 'columns': columns,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': data.length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [{targets: '_all', className: 'dt-center'}]",
" });",
" } else {",
" var subtable = $(id).DataTable({",
" 'data': data,",
" 'columns': columns,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': data.length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [",
" {targets: -1, visible: false},",
" {targets: 0, orderable: false, className: 'details-control'},",
" {targets: '_all', className: 'dt-center'}",
" ]",
" }).column(0).nodes().to$().css({cursor: 'pointer'});",
" }",
"};",
"",
"// --- display the child table on click --- //",
"// array to store id's of already created child tables",
"var children = [];",
"table.on('click', 'td.details-control', function(){",
" var tbl = $(this).closest('table'),",
" tblId = tbl.attr('id'),",
" td = $(this),",
" row = $(tbl).DataTable().row(td.closest('tr')),",
" rowIdx = row.index();",
" if(row.child.isShown()){",
" row.child.hide();",
" td.html('&oplus;');",
" } else {",
" var childId = tblId + '-child-' + rowIdx;",
" if(children.indexOf(childId) === -1){",
" // this child has not been created yet",
" children.push(childId);",
" row.child(formatHeader(row.data(), childId)).show();",
" td.html('&CircleMinus;');",
" formatDatatable(row.data(), childId, rowIdx);",
" }else{",
" // this child has already been created",
" row.child(true);",
" td.html('&CircleMinus;');",
" }",
" }",
"}); ",
"// --- add radio button functionality --- //",
"table.rows().every(function(i, tab, row) {",
" var $this = $(this.node());",
" $this.attr('id', this.data()[1]);",
" $this.addClass('shiny-input-radiogroup');",
" });",
" Shiny.unbindAll(table.table().node());",
" Shiny.bindAll(table.table().node());")


datatable(
values$tab,
escape = F,
rownames = F,
callback = callback,
options = list(
dom = 't',
paging = FALSE,
ordering = FALSE,
paging = FALSE,
searching = FALSE,
columnDefs = list(
list(
visible = FALSE,
targets = c(c(1, ncol(values$tab)-1)) # do not show certain ID variables, we do not need
),
list(
orderable = FALSE,
className = "details-control",
targets = 0
),
list(
className = "dt-left",
targets = "_all"
)
)
)

) },
server = F)


observeEvent(input$remove_button_Tab1, {
myTable <- values$tab
s <- as.numeric(strsplit(input$remove_button_Tab1, "_")[[1]][2])
myTable <- filter(myTable, id != s)
replaceData(proxyTable, myTable, resetPaging = FALSE)
values$tab <- myTable
})

list_results <- reactive({
list_values <- list()
for (i in unique( values$tab$let_rowid)) {
list_values[[i]] <- paste0(i, ": ", input[[i]])

}
list_values
})

output$sel = renderPrint({
list_results()
})



####################################
## this simpler version does work ##
####################################


# removed for now


}
)
我尝试的另一件事(但没有奏效)
我现在也尝试了这里给出的解决方案: https://stefanengineering.com/2019/07/06/delete-rows-from-shiny-dt-datatable/ ,但这给出了完全相同的问题。

最佳答案

您分配错误 id JavaScript 回调中的元素,因为在您的表中,第一列实际上是 &oplus列,但您想要第二列。
从而改变这个

table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]); // this.data()[0] refers to the firts column, i.e. &oplus;
$this.addClass('shiny-input-radiogroup');
});
对此:
table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[1]); // the id is in the second column in your case
$this.addClass('shiny-input-radiogroup');
});

关于javascript - 如何从 Shiny 的 DT 中的单选按钮(使用 JS 回调制作)访问用户输入,并在一个 DT 中有不同的 JS 元素?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66264935/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com