1

In reactable it is possible to use JavaScript functions, like in this example:

reactable(iris, columns = list(
  Species = colDef(
    cell = JS("function(cellInfo) {
      return '<b>' + cellInfo.value + '</b>'
    }")
  )
))

However, all examples I saw in the documentation use string passed to the JS() function. I think that taking into account the readability and conveniences, it would be better to store the JS function in separate .js file. Is it true? And if yes, how to achieve this? I mean - how to call then this function? Based on the example above.

gss
  • 1,334
  • 6
  • 11

2 Answers2

1

You can create a separate file (script.js), and then read in your JavaScript using readLines():

script.js

function(cellInfo) {
      return '<b>' + cellInfo.value + '</b>'
}

reactable.r

library(reactable)

js <- readLines('script.js')

reactable(iris, columns = list(
  Species = colDef(
    cell = JS(js)
  )
))
Matt
  • 7,255
  • 2
  • 12
  • 34
  • 1
    Thank you! This is something I was thinking about and wasn't sure if `readLines` is something which should be use for document containing source code, but well, source code is a text :) – gss May 26 '22 at 09:13
1

Writing JavaScript as an inline string in R is always going to be icky, but I think it makes sense when you have a short, one-off function that's only used for your table. Colocating logic can also help with readability, since you don't have to jump around to different files. And the code is completely self-contained so you can change it or move it around easily.

With that said, I do prefer to move JavaScript to an external .js file when it gets larger and more complicated, and/or when it has to be reused in multiple places. With an external file, you get proper syntax checking and highlighting, linting, auto-formatting, an opportunity to write tests, and freedom from horrible string escaping headaches.

R Markdown

With R Markdown documents, you can use a js language chunk to include an external script (useful for optionally showing the actual JS code with echo=TRUE):

```{js, file='script.js', echo=FALSE}
```

Note: this example assumes script.js is in the same directory as the R Markdown document, so change this to path/to/somewhere-else/script.js for a different location.

You can also include the script in the document using a <script> HTML tag:

<script src="script.js"></script>

This HTML goes directly in the document, outside of any code chunk. R Markdown understands <script> tags and will embed the contents of your script within the rendered HTML document.

Shiny

For Shiny apps, you have more options, and I'll refer to the article on Packaging JavaScript code for Shiny for a detailed summary of each option.

R scripts

R scripts are more tricky, and it depends where you're running the code from - is it from the RStudio IDE, or R from the command line, or RGui?

The RStudio IDE Viewer serves rendered HTML from a local web server, which means browser security features will probably prevent you from directly including a <script> file from the local filesystem.

Instead, a more robust way to include external scripts is to use htmltools::htmlDependency(), so the external script works in both the RStudio IDE and everywhere else:

library(reactable)
library(htmltools)

html <- browsable(tagList(
  htmlDependency(
    # name and version: doesn't really matter, can be anything
    "my-script", "0.1.0",
    # src: path to directory containing the script (e.g., the current directory '.')
    src = normalizePath("."),
    # script: filename of script to include
    script = "script.js",
    # Exclude all other files in src directory
    all_files = FALSE
  ),

  reactable(...)
))

# View it directly from an R console
html

# Or save it to an HTML file
save_html(html, "table.html")

This also assumes script.js is in the current running directory, so change src to the location of wherever your script is if needed.

And, I also like Matt's answer of reading the external script into R. It's simple and probably works well in most cases.

If you go with the htmlDependency() approach, you may get additional benefits like automatic deduplication of the script if it's included multiple times in the document. It'll work better if you're reusing the script in multiple tables or writing a package that produces tables with JavaScript dependencies.

greg L
  • 4,034
  • 1
  • 19
  • 18
  • Thanks! One thing which makes me uncertain: `htmlDependency` returns list, but (let's take JS script from your package reactable as an example which I showed in my question) how to use it later for chosen column in reactable object? This list does not return element where will be the content of .js file, so I can't use something like `JS(htmlDependency()$content)`. I mean - I didn't check example from your answer, but will it really work and target the desired column, i.e. `Species` col from `iris`? (I'm thinking during writing this); probably not and in .js should `getDocumentById...`? – gss May 31 '22 at 10:59
  • 1
    Oh yes, the `htmlDependency` is just a way to ensure your JavaScript script is included in the document, and can't be used directly in R like that. Instead, what you'd do is declare your function in the JS file with a function name, then use that function in your `JS()` call in R. Here's an example with R Markdown: https://glin.github.io/reactable/articles/custom-filtering.html#range-filter-react. Everything in the JS script is available globally, so using the function will just work. – greg L Jun 01 '22 at 01:51
  • But since everything is global, there is a downside with potential naming collisions, so you want to make sure to use unique function names, or namespace your functions (probably not necessary unless you're making a package though) – greg L Jun 01 '22 at 01:52
  • Thank you! @greg L I don't want to take too much of your time, but do you have some link / resources for this: "namespace your functions"? Because indeed, I'm making a package with reusable module. And you mean namespacing JavaScript functions? Sounds tricky to use the same `id` as passed to the module as a prefix. But if this is too broad topic, that's fine, you can ignore my comment since most probably I would go with the option with `readLines`. – gss Jun 01 '22 at 06:24
  • Unfortunately I don't, but I would probably start by searching along the lines of "javascript namespacing". There are many ways to namespace, but the gist is to pick one (hopefully) globally unique name, and put all your functions within that name. For example, the reactable JavaScript API uses `Reactable.downloadDataCSV()`, `Reactable.setSearch()`, etc. Shiny uses `Shiny.sendInputValue()`. Everything is namespaced to a single global object like `Reactable` or `Shiny` to minimize conflicts. – greg L Jun 02 '22 at 01:04
  • A very simple example would be a script that contains something like: `const MyNamespace = { formatValue: function(cellInfo) { return cellInfo.value } }`. Then to call the namespaced function from an htmlwidget in R, assuming the script is included in the document, `colDef(cell = JS("MyNamespace.formatValue"))` – greg L Jun 02 '22 at 01:06