Search code examples
javascripthtmljqueryrdt

character string from R as HTML in JS()


I'm trying to make a datatable object that has a child table with each row. I'm following the format of a Child Rows example given by Yihui Xie.

The example shows how to pass a simple string in, and I can even do rudimentary tables. But I don't want to hardcode a lot of HTML, since R has great facilities to write HTML for me.

My problem is that if I user R to generate an character string as HTML, when I pass it into the datatable object through JS, it reads it as literal text instead of rendering it as HTML.

I'm sure it's a simple solution, but using htmltools::htmlPreserve and htmltools::HTML don't change anything, and I suspect I'll have to do something within the javascript.

DT::datatable({
    mtcars$rows <- 
      paste0("<tr><td>Gas Mileage</td><td>", mtcars$mpg, "</td></tr>",
             "<tr><td>Quarter Mile</td><td>", mtcars$qsec, "</td></tr>")
    cbind(' ' = '&oplus;', mtcars)
  },
  escape = c(-2, -13),
  selection = "single",
  options = list(
    columnDefs = list(
      list(visible = FALSE, targets = c(0, 13)),
      list(orderable = FALSE, className = 'details-control', targets = 1)
      )
    ),
  callback = JS("
    table.column(1).nodes().to$().css({cursor: 'pointer'});
    var format = function(d) {
      return '<table>' + 
             '<tr><td>Variable</td><td>Value</td></tr>' + 
             d[13] + 
             '</table>';
    };
    table.on('click', 'td.details-control', function() {
      var td = $(this), row = table.row(td.closest('tr'));
      if (row.child.isShown()) {
        row.child.hide();
        td.html('&oplus;');
      } else {
        row.child(format(row.data())).show();
        td.html('&CircleMinus;');
      }
    });"
  )
)

enter image description here


Solution

  • Try replacing escape = c(-2, -13) with escape = FALSE.


    Looking at the source code, the escape variable is passed into the escapeData function of datatables which looks like this. Hopefully it'll give you a bit of guidance as to how to correctly custom-specify what should/shouldn't be escaped. :)

    # `i` here is your `escape` variable which can be either TRUE/FALSE, 
    # indices, or colnames.
    escapeData = function(data, i, colnames) {
      if (is.null(data) || prod(dim(data)) == 0 || identical(i, FALSE)) return(data)
    
      // see below for definition of convertIdx
      i = convertIdx(i, colnames, ncol(data))
      # only escape character columns (no need to escape numeric or logical columns)
      data[i] = lapply(data[i], function(x) {
        if (is.character(x) || is.factor(x)) htmlEscape(x) else x
      })
      data
    }
    
    
    # convertIdx looks like this: 
    # convert character indices to numeric
    convertIdx = function(i, names, n = length(names), invert = FALSE) {
      if (!is.character(i)) return({
        if (invert) {
          if (is.numeric(i)) -i else if (is.logical(i)) !i else {
            stop('Indices must be either character, numeric, or logical')
          }
        } else i
      })
      if (is.null(names)) stop('The data must have column names')
      o = setNames(seq_len(n), names)
      i = o[i]
      if (any(is.na(i)))
        stop("Some column names in the 'escape' argument not found in data")
      if (invert) o[-i] else i
    }