使用ajax的儿童行闪亮数据表

时间:2014-05-13 15:31:42

标签: jquery ajax r datatables shiny

我正在尝试使用数据表库来实现更多自定义。

以下是我正在尝试制作的示例。 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请求。

1 个答案:

答案 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应用程序示例如下:

ui.R

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将数据作为行数组而不是对象发送。

server.R

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请求来获取所需的数据。

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

server.R

{{1}}