我正在尝试使用数据表库来实现更多自定义。
以下是我正在尝试制作的示例。 https://datatables.net/examples/api/row_details.html 请注意,我在不同的data.frame R变量中有详细信息。像这样
A= data.frame(Name = c("Airi Satou", "Angelica Ramos","Paul Byrd")
, Position = c("Accountant","Accountant", "CEO")
, Office = c("Tokyo", "Tokyo", "New York"))
A.detail= data.frame(Name = c("Airi Satou", "Angelica Ramos")
, Extension= c("5407c", "8422")
, salary = c(16000, 20000))
我不喜欢合并两个data.frame变量,如果可以在没有合并的情况下这样做,因为计算时间。显然,有些行可能没有任何细节。
我可以在数据表中选择一行,并通过将其绑定为输入将行信息发送给R(感谢https://github.com/toXXIc/datatables.Selectable/) 然后我可以从第二个data.frame变量中找到与R中选定行相关的详细信息。 但我不知道如何将此发送回html(在所选行下)显示。我已经将第一个表绑定为shinyoutput所以我;我不确定是否可以传递另一个数据来再次更改此输出。
也许我应该在单击详细信息按钮时使用ajax来请求更多数据,但我不知道如何在闪亮的情况下执行ajax请求。
答案 0 :(得分:12)
在回答您的问题之前,我想指出CRAN上当前版本的Shiny(0.10.1)使用旧版本的DataTables.js(1.0.9),而您提到的示例使用DataTables。 js 1.10。 DataTables 1.10中有相当大比例的API与版本1.0.9不兼容。
您可以在Github上查看此拉取请求:https://github.com/rstudio/shiny/pull/558,它提供DataTables.js 1.10支持。
首先,让我们稍微了解一下如何在Shiny中呈现数据表。
该示例使用AJAX请求从服务器URL“拉”数据,然后将数据绑定到表模板。这就是所谓的服务器端数据呈现。
Shiny还使用服务器端数据呈现。但是,您提供的示例与Shiny之间的主要区别在于,Shiny中的数据传输对您来说是透明的。
从技术上讲,Shiny通过调用shiny:::registerDataObj()
为AJAX请求创建了一个JSON API。您可以在此处找到构建自定义AJAX请求的示例:http://shiny.rstudio.com/gallery/selectize-rendering-methods.html。
示例和Shiny之间的另一个区别是,它们将表内容编码为JSON blob,后者将在R代码中反映出来。 该示例使用普通对象对每一行进行编码。例如,第一行编码为:
{
"name": "Tiger Nixon",
"position": "System Architect",
"salary": "$320,800",
"start_date": "2011\/04\/25",
"office": "Edinburgh",
"extn": "5421"
},
而Shiny将data.frame
的每一行编码为数组,例如,
["Tiger Nixon", "System Architect",
"$320,800", "2011\/04\/25", "Edinburgh", "5421"]
原始 JSON数据的差异会影响我们稍后实现format()
功能的方式。
最后,该示例使用固定的HTML <table>
模板来呈现数据表。您可能已经注意到模板中只包含可见列(例如,扩展号列不在<table>
模板中);而Shiny为您创建模板,而您无法决定如何执行数据绑定(例如{ "data": "name" },
)。
注意:下面的R代码使用了Shiny的开发分支,您可以在上面的请求链接中找到它。
虽然我们无法决定将哪些列绑定到哪些数据,但是当您调用columnDefs
函数时,我们可以通过指定DataTable()
选项来选择要隐藏的列。您可以通过将https://datatables.net/reference/option/
包裹在list
中来传递library(shiny)
format.func <- "
<script type='text/javascript'>
function format ( d ) {
return '<table cellpadding=\"5\" cellspacing=\"0\" border=\"0\" style=\"padding-left:50px;\">'+
'<tr>'+
'<td>Full name:</td>'+
'<td>'+d[1]+'</td>'+
'</tr>'+
'<tr>'+
'<td>Extension number:</td>'+
'<td>'+d[4]+'</td>'+
'</tr>'+
'</table>';
}
</script>
"
shinyUI(
fluidPage(
h5("Data table"),
dataTableOutput("dt"),
tags$head(HTML(format.func))
)
)
中定义的任何选项。
使用示例数据的Shiny应用程序示例如下:
format()
这里没有什么特别的,除了我相应地更改了library(shiny)
library(dplyr)
shinyServer(function(input, output, session) {
A <- data.frame(Name = c("Airi Satou", "Angelica Ramos","Paul Byrd"),
Position = c("Accountant","Accountant", "CEO"),
Office = c("Tokyo", "Tokyo", "New York"))
A.detail <- data.frame(Name = c("Airi Satou", "Angelica Ramos"),
Extension = c("5407c", "8422"),
Salary = c(16000, 20000))
# You don't necessarily need to use left_join. You can simply put every column,
# including the columns you would by default to hide, in a data.frame.
# Then later choose which to hide.
# Here an empty column is appended to the left to mimic the "click to expand"
# function you've seen in the example.
A.joined <- cbind("", left_join(A, A.detail, by="Name"))
columns.to.hide <- c("Extension", "Salary")
# Javascript uses 0-based index
columns.idx.hidden <- which(names(A.joined) %in% columns.to.hide) - 1
# Everytime a table is redrawn (can be triggered by sorting, searching and
# pagination), rebind the click event.
draw.callback <- "
function(settings) {
var api = this.api();
var callback = (function($api) {
return function() {
var tr = $(this).parent();
var row = $api.row(tr);
if (row.child.isShown()) {
row.child.hide();
tr.removeClass('shown');
}
else {
row.child(format(row.data())).show();
tr.addClass('shown');
}
}
})(api);
$(this).on('click', 'td.details-control', callback);
}"
# wrap all options you would like to specify in options=list(),
# which will be converted into corresponding JSON object.
output$dt <- renderDataTable(A.joined,
options=list(
searching=F,
columnDefs=list(
list(targets=0,
title="", class="details-control"),
list(targets=columns.idx.hidden,
visible=F)
),
drawCallback=I(draw.callback)
))
})
函数,因为如前所述,Shiny将数据作为行数组而不是对象发送。
session$registerDataObj
现在,如果您单击数据表的第一个(空)列(因为我没有编写CSS),您应该能够看到扩展区域中显示的额外信息。
上述解决方案涉及将所有信息发送到客户端,但在大多数用例中,用户可能无需查看隐藏信息。基本上我们最终会向客户端发送大量冗余数据。
更好的解决方案是在Shiny中实现一个AJAX请求处理程序,它只在需要时(即用户请求时)返回信息。
要实现AJAX请求处理程序,可以使用<input>
。此函数在唯一 URL处注册请求处理程序,并返回此URL。
要调用此注册请求处理程序,您需要先将此AJAX URL发送到客户端。
下面我破解了一个快速解决方案:基本上你在网页上创建一个隐藏的change
元素,你可以绑定一个<input>
事件监听器。 Shiny服务器通过函数调用session$sendInputMessage
向客户端发送消息来更新此<input>
元素的值。收到消息后,它会更改library(shiny)
format.func <- "
<script type='text/javascript'>
var _ajax_url = null;
function format ( d ) {
// `d` is the original data object for the row
return '<table cellpadding=\"5\" cellspacing=\"0\" border=\"0\" style=\"padding-left:50px;\">'+
'<tr>'+
'<td>Full name:</td>'+
'<td>'+d.Name+'</td>'+
'</tr>'+
'<tr>'+
'<td>Extension number:</td>'+
'<td>'+d.Extension+'</td>'+
'</tr>'+
'</table>';
}
$(document).ready(function() {
$('#ajax_req_url').on('change', function() { _ajax_url = $(this).val()});
})
</script>
"
shinyUI(
fluidPage(
# create a hidden input element to receive AJAX request URL
tags$input(id="ajax_req_url", type="text", value="", class="shiny-bound-input", style="display:none;"),
h5("Data table"),
dataTableOutput("dt"),
tags$head(HTML(format.func))
)
)
元素的值,从而触发事件侦听器。然后我们可以正确设置AJAX请求URL
之后,您可以启动任何正常的AJAX请求来获取所需的数据。
library(shiny)
library(dplyr)
shinyServer(function(input, output, session) {
# extra more.details dummy column
A <- data.frame(more.details="", Name = c("Airi Satou", "Angelica Ramos","Paul Byrd"),
Position = c("Accountant","Accountant", "CEO"),
Office = c("Tokyo", "Tokyo", "New York"))
A.detail <- data.frame(Name = c("Airi Satou", "Angelica Ramos"),
Extension = c("5407c", "8422"),
Salary = c(16000, 20000))
draw.callback <- "
function(settings) {
var api = this.api();
var callback = (function($api) {
return function() {
var tr = $(this).parent();
var row = $api.row(tr);
if (row.child.isShown()) {
row.child.hide();
tr.removeClass('shown');
}
else {
// we can use the unique ajax request URL to get the extra information.
$.ajax(_ajax_url, {
data: {name: row.data()[1]},
success: function(res) {
row.child(format(res)).show();
tr.addClass('shown');
}
});
}
}
})(api);
$(this).on('click', 'td.details-control', callback);
}"
ajax_url <- session$registerDataObj(
name = "detail_ajax_handler", # an arbitrary name for the AJAX request handler
data = A.detail, # binds your data
filter = function(data, req) {
query <- parseQueryString(req$QUERY_STRING)
name <- query$name
# pack data into JSON and send.
shiny:::httpResponse(
200, "application/json",
# use as.list to convert a single row into a JSON Plain Object, easier to parse at client side
RJSONIO:::toJSON(as.list(data[data$Name == name, ]))
)
}
)
# send this UNIQUE ajax request URL to client
session$sendInputMessage("ajax_req_url", list(value=ajax_url))
output$dt <- renderDataTable(A,
options=list(
searching=F,
columnDefs=list(
list(targets=0,
title="", class="details-control")
),
drawCallback=I(draw.callback)
))
})
{{1}}