使用ajax的带有子行的闪亮数据 [英] Shiny datatable with child rows using ajax

查看:476
本文介绍了使用ajax的带有子行的闪亮数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用数据库库进行更多的定制化。



这是我试图做的一个例子。
https://datatables.net/examples/api/row_details.html
请注意,我有不同的data.frame R变量的详细信息。像这样

  A = data.frame(Name = c(Airi Satou,Angelica Ramos,Paul Byrd )
,Position = c(会计师,会计师,CEO)
,Office = c(东京,东京,纽约))
A .detail = data.frame(Name = c(Airi Satou,Angelica Ramos)
,Extension = c(5407c,8422)
,salary = c(16000,20000 ))

我不喜欢合并两个data.frame变量,如果可以做这个没有合并,因为计算时间。显然,一些行可能没有任何细节。



我可以在数据表中选择一行,并通过将其绑定到输入来将行信息发送到R(感谢< a href =https://github.com/toXXIc/datatables.Selectable/ =nofollow> https://github.com/toXXIc/datatables.Selectable/ )
然后我可以从第二个data.frame变量中找到与R中所选行有关的细节。
但我不知道如何发送回来显示在html(在所选行下)。我已经绑定第一个表作为闪光输出,所以我不知道如果我可以传递另一个数据来更改此输出。



也许我应该使用ajax来请求更多的数据当点击详细按钮时,但我不知道如何在闪亮的情况下执行ajax请求。

解决方案

在回答您的问题之前,我想指出,目前版本的Shiny(0.10.1)在CRAN上使用的是旧版本的DataTables.js(1.0.9),而您提到的示例使用DataTables.js 1.10。 DataTable 1.10中有相当一部分API与1.0.9版不兼容。



您可以在Github上查看拉请求 https://github.com/rstudio/shiny/pull/558 ,它提供DataTables.js 1.10支持。



< hr>

首先,我们来解释一下如何在Shiny中呈现数据表。



该示例使用AJAX请求从服务器URL拉数据,然后将绑定数据到表模板。这就是所谓的服务器端数据呈现



Shiny还使用服务器端数据呈现。但是,您提供的示例与Shiny之间的主要区别在于,Shiny中的数据传输对您来说是透明的。



技术上,在引擎盖下,Shiny通过调用 shiny来创建一个用于AJAX请求的JSON API ::: registerDataObj()。您可以在这里找到构建自定义AJAX请求的示例: http://闪亮。 rstudio.com/gallery/selectize-rendering-methods.html



示例和Shiny之间的另一个区别将在后面的R代码中反映出来是他们如何将表格内容编码成JSON Blob。
该示例使用纯对象对每行进行编码。例如,第一行编码为:




{
name:Tiger Nixon,
position:System Architect,
salary:$ 320,800,
start_date:2011 \ / 04\ / 25,
:爱丁堡,
extn:5421
},



Shiny将 data.frame 的每一行编码为数组,例如,




[Tiger Nixon,系统架构师,
$ 320,800,2011 \ / 04\ 25,爱丁堡,5421 ]



原始 JSON数据的区别会影响我们如何实现 format()函数。



最后,该示例使用固定的HTML < table> 模板来呈现数据表。您可能已经注意到,模板中只包含可见的列(例如,扩展名号列不在< table> 模板中);而Shiny为您创建了模板,您无法确定如何执行数据绑定(例如 {data:name},)。 p>




注意:下面的R代码使用了Shiny的开发分支,您可以在上述拉取请求链接中找到。



尽管我们无法确定哪些列绑定到什么数据,但我们可以通过指定 DataTable()函数时,可以使用columnDefs 选项。您可以将 https://datatables.net/reference/option/ 中定义的任何选项传递到列表在R中。



使用示例数据的Shiny应用程序的一个例子是:



ui。

$ b

 库(闪亮)

format.func< -
< script type ='text / javascript'>
function format(d){
return'< table cellpadding = \5\cellspacing = \0\border = \\0\style = \padding-left:50px; \>'+
'< tr>'+
'< td>全名: td>'+
'< td>'+ d [1] +'< / td>'+
'< / tr>'+
'< tr> +
'< td>分机号码:< / td>'+
'< td>'+ d [4] +'< / td>'+
' ; / tr>'+
'< / table>';
}
< / script>


shinyUI $ b fluidPage(
h5(Data table),
dataTableOutput(dt),
标签$ head(HTML(format.func))


除了我更改了格式之外,没有什么特别之处()相应地,因为如前所述,Shiny将数据作为行数组而不是对象发送。



server.R



 库(闪亮)
库(dplyr)

ShinyServer(function(input,output,session){
A< - data.frame(Name = c(Airi Satou,Angelica Ramos,Paul Byrd),
职位= c(会计师,会计师,CEO ),
Office = c(Tokyo,Tokyo,New York))

A.detail< - data.frame(Name = c(Airi Satou ,Angelica Ramos),
Extension = c(5407c,8422),
工资= c(16000,20000))

#一定需要使用left_jo在。你可以简单地把每个列
#,包括你默认的列隐藏在数据框架中。
#然后再选择哪个隐藏。
#这里有一个空列附加到左边,以模仿click to expand
#函数,你在示例中看到过。
A.加入< - cbind(,left_join(A,A.detail,by =Name))

columns.to.hide< - c ,Salary)
#Javascript使用基于0的索引
columns.idx.hidden< - 其中(%(.joined)%in%columns.to.hide) - 1

#每次重绘一个表(可以通过排序,搜索和
#pagination触发),重新绑定点击事件。

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);
}
#包装你想在options = list中指定的所有选项(),
#将被转换成相应的JSON对象。
输出$ dt< - renderDataTable(A.joined,
options = list(
searching = F,
columnDefs = list(
list(targets =
title =,class =details-control),
list(targets = columns.idx.hidden,
visible = F)
),
drawCallback = I(draw.callback)
))
})

现在如果您单击数据表的第一个(空)列(因为我没有编写CSS),则应该能够看到扩展区域中显示的额外信息。






编辑:懒惰加载更多详细信息



以上解决方案涉及向客户端发送所有信息,尽管在大多数用例中,用户可能无需查看隐藏信息。基本上我们最终会向客户端发送大量冗余数据。



更好的解决方案是在Shiny中实现一个AJAX请求处理程序,它只返回信息需要(即按用户请求)。



要实现AJAX请求处理程序,可以使用 session $ registerDataObj 。该函数以唯一 URL注册请求处理程序,并返回此URL。



为了调用此注册的请求处理程序,您需要首先将此AJAX URL发送给客户端。



下面我劫持了一个快速的解决方案:基本上你创建一个隐藏的< input> 您可以绑定更改事件侦听器。 Shiny服务器通过函数调用会话$ sendInputMessage < input> 元素的值>。一旦接收到该消息,它将更改< input> 元素的值,从而触发事件侦听器。然后我们可以正确设置AJAX请求URL



之后,您可以启动任何正常的AJAX请求来获取所需的数据。



ui.R



 库(闪亮)

format.func< -
< script type ='text / javascript'>
var _ajax_url = null;

函数格式(d){
//`是行的原始数据对象
return'< table cellpadding = \5\cellspacing = \0\border = \0\style = \padding -left:50px; \>'+
'< tr>'+
'< td>全名:< / td>'+
'< td& ;'+ d.Name +'< / td>'+
'< / tr>'+
'< tr>'+
'< td>分机号码:< ; / td>'+
'< td>'+ d.Extension +'< / td>'+
'< / tr>'+
'< / table> ';
}

$(document).rea dy(function(){
$('#ajax_req_url')。on('change',function(){_ajax_url = $(this).val()});
})
< / script>


ShinyUI(
fluidPage(
#创建一个隐藏的输入元素来接收AJAX请求URL
标签$ input(id =ajax_req_url type =text,value =,class =shiny-bound-input,style =display:none;),

h5(Data table),
dataTableOutput(dt),
标签$ head(HTML(format.func))




server.R



 库(闪亮)
库(dplyr )

ShinyServer(function(input,output,session){
#extra more.details dummy column
A< - data.frame(more.details =名称= c(Airi Satou,Angelica Ramos,Paul Byrd),
职位= c(会计师,会计师,CEO),
Office = c东京,东京,纽约)

A.detail< - data.frame(Name = c(Airi Satou,Angelica Ramos),
Extension = c(5407c,8422),
工资= 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 {
//我们可以使用唯一的ajax请求URL来获取额外的信息
$ .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,#AJAX请求处理程序的任意名称
data = A.detail ,#绑定你的数据
filter = function(data,req){
query< - parseQueryString(req $ QUERY_STRING)
name< - query $ name

#将数据打包成JSON并发送
shiny ::: httpResponse(
200,application / json,
#使用as.list将单个行转换为JSON Plain Object ,更容易在客户端解析
RJSONIO ::: toJSON(as.list(data [data $ Name == name,]))

}


#将这个UNIQUE ajax请求URL发送给客户端
会话$ sendInputMessage(ajax_req_url,list(value = ajax_url))

输出$ dt< - renderDataTable A,
options = list(
searching = F,
columnDefs = list(
list (目标= 0,
title =,class =details-control)
),
drawCallback = I(draw.callback)
))
})


I'm trying to use datatables library for shiny with more customization.

Here is the example that I'm trying to make. https://datatables.net/examples/api/row_details.html Note that I have detail info in different data.frame R variables. like this

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))

I don't like to merge two data.frame variables, if it is possible to do this without merge, because of the computation time. Obviously, some of rows may not have any details.

I can select a row in data table and send the line information to R by binding this as input (thanks to https://github.com/toXXIc/datatables.Selectable/) Then I can find details relevant to selected line in R from the second data.frame variable. but I don't know how to send this back to show on the html (under the selected row). I already binded the first table as shinyoutput so I;m not sure if I can pass another data to change this output again.

Maybe I should use ajax to request more data when detail button is clicked, but I don't know how to do ajax request in shiny.

解决方案

Before answering your questions, I would like to point out that the current version of Shiny (0.10.1) on CRAN uses an older version of DataTables.js (1.0.9), whereas the example you have mentioned uses DataTables.js 1.10. There is a considerable proportion of API in DataTables 1.10 that is incompatible with version 1.0.9.

You can check out this pull request on Github: https://github.com/rstudio/shiny/pull/558 , which provides DataTables.js 1.10 support.


First, let's digress a little bit to understand how a data table is rendered in Shiny.

The example uses AJAX request to "pull" data from a server URL, and subsequently binds the data to the table template. This is what so-called server-side data rendering.

Shiny also uses server-side data rendering. However, the major difference between the example you've provided and Shiny, is that the data transferring in Shiny is transparent to you.

Technically, under the hood, Shiny creates an JSON API for AJAX requests by calling shiny:::registerDataObj(). You can find an example of constructing your customized AJAX request here: http://shiny.rstudio.com/gallery/selectize-rendering-methods.html .

Another difference between the example and Shiny, which will later be reflected in the R code, is how they encodes the table content into a JSON blob. The example encodes each line using plain objects. For instance, the first row is encoded as:

{ "name": "Tiger Nixon", "position": "System Architect", "salary": "$320,800", "start_date": "2011\/04\/25", "office": "Edinburgh", "extn": "5421" },

Whereas Shiny encodes each row of your data.frame as an array, e.g., something like,

["Tiger Nixon", "System Architect", "$320,800", "2011\/04\/25", "Edinburgh", "5421"]

The difference in the raw JSON data affects how we would implement the format() function later.

Finally, the example uses a fixed HTML <table> template to render the data table. You may have noticed that only visible columns are included in the template (for example, the Extension number column is not in the <table> template); whereas Shiny creates the template for you and you don't get to decide how your data binding (e.g. { "data": "name" },) is performed.


NOTE: The R code below uses a development branch of Shiny which you can find in the above pull request link.

Although we don't get to decide which columns to bind to what data, we can choose which columns to hide by specifying the columnDefs options when you call the DataTable() function. You can pass whatever options that are defined in https://datatables.net/reference/option/ by wrapping them in a list in R.

An example of a Shiny app using your example data is:

ui.R

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))
    ) 
)

There is nothing special here except that I've changed the format() function accordingly because, as mentioned before, Shiny sends data as row arrays instead of objects.

server.R

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)
    ))
})

Now that if you click on the first (empty) column (because I've written no CSS) of your data table, you should be able to see the extra information shown in the expanded area.


EDIT: Lazy loading of "more details" information

Above solution involves sending all information to the client, although in most use cases the user may not bother to view the hidden information. Essentially we end up with sending a lot of redundant data to the client side.

A better solution is to implement a AJAX request handler in Shiny, which only returns the information when needed (i.e. as the user requests).

To implement a AJAX request handler, one can use session$registerDataObj. This function registers a request handler at a unique URL, and returns this URL.

In order to call this registered request handler, you need to first send this AJAX URL to the client.

Below I hacked a quick solution: basically you create a hidden <input> element on the webpage on which you can bind a change event listener. The Shiny server updates this <input> element's value by sending a message to the client via the function call session$sendInputMessage. Once the message is received, it changes the value of the <input> element, triggering the event listener. We can then set up the AJAX request URL properly

Afterward, you can initiate any normal AJAX requests to fetch the data you need.

ui.R

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))
    ) 
)

server.R

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)
    ))
})

这篇关于使用ajax的带有子行的闪亮数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆