in `Shiny` `JS` getting passed as a string
timelyportfolio opened this issue · 18 comments
@jcheng5, @ramnathv, @yihui | I wondered if you might be able to add some insight on this strange situation. When using parcoords
, the JS_EVALS
get passed as a character
and fail when rendered with renderParcoords
in Shiny, but work perfectly even in Shiny when rendered statically with parcoords
. Here is a simple example that fails on my machine. sessionInfo()
pasted below code. I'll keep digging but I have not found anything so far.
library(shiny)
library(parcoords)
p1 <- parcoords(
mtcars,
,color = list(
colorScale=htmlwidgets::JS('d3.scale.category10()')
, colorBy = "cyl"
)
)
# test p1 ; should color by cyl
p1
# JS_evals
htmlwidgets:::JSEvals(p1$x)
ui <- shinyUI(fluidPage(
tags$h1("shiny")
,parcoordsOutput("DiamondPlot")
,tags$h1("static")
,p1
))
server <- function(input, output, session) {
output$DiamondPlot <- renderParcoords({
p1
})
}
shinyApp(ui,server)
R version 3.1.2 (2014-10-31)
Platform: x86_64-w64-mingw32/x64 (64-bit)
locale:
[1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252
[3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C
[5] LC_TIME=English_United States.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] parcoords_0.1 shiny_0.11.1.9002
loaded via a namespace (and not attached):
[1] digest_0.6.4 htmltools_0.2.7 htmlwidgets_0.3.2 httpuv_1.3.2 jsonlite_0.9.14
[6] mime_0.2 R6_2.0.1 Rcpp_0.11.4.6 RJSONIO_1.3-0 tools_3.1.2
[11] xtable_1.7-4 yaml_2.1.13
It works for me. What error are you talking about?
So you get two parcoords? If so this is what I feared and will make things more difficult. I and another user get an empty box on top (see #4). I just hopped off my computer but the error I get is colorScale not a function because it is a string.
Yes. I get two parcoords. My version of htmltools
is different. I don't know if any changes there affect this situation.
never mind the DT
works fine.
library(shiny)
library(DT)
d1 <- datatable(head(iris, 20), options = list(
initComplete = JS(
"function(settings, json) {",
"$(this.api().table().header()).css({'background-color': '#000', 'color': '#fff'});",
"}")
))
d1
ui <- shinyUI(fluidPage(
tags$h1("shiny")
,dataTableOutput("testDT")
,tags$h1("static")
,d1
))
server <- function(input, output, session) {
output$testDT <- renderDataTable({
d1
})
}
shinyApp(ui,server)
I am able to reproduce this error with shiny_0.11.1.9002
. It worked fine with shiny_0.11.1
. So the issue must be some changes made between. It works with shiny_0.11.1.9000
. So must be something that changed after that. I suspect it is the switch from RJSONIO to jsonlite.
CONFIRMED: It works with all versions prior to this commit.
Yes, I'm pretty sure we will need a few changes in our widget packages after switching to jsonlite.
Sure. I will start working on a jsonlite
branch of htmlwidgets
. Are there known differences in mappings between RJSONIO
and jsonlite
, specific to the way they are used within shiny
?
Ok, good to know. I thought @yihui already had a jsonlite
pull from November.
I just rebased ramnathv/htmlwidgets#28 and I will have to figure out the consequences of switching to jsonlite in htmlwidgets.
Great! We can test things out with your jsonlite
branch before merging changes back in. We would also need to list down common changes that package authors would have to make in order to switch their widgets to the new version.
I think I've found the difference. Inspecting the JSON in the static version (generated by RJSONIO?), I see:
"evals":["options.color.colorScale"]
And for the Shiny version, with options(shiny.trace=TRUE)
, I see:
"evals":"options.color.colorScale"
This probably has to do with the behavior when auto_unbox=TRUE
. What is the structure of the object before it's converted to JSON?
Previously, Shiny used RJSONIO::toJSON()
; now it uses a wrapper function around jsonlite::toJSON()
, with a bunch of options set: https://github.com/rstudio/shiny/blob/e4a211b/R/shiny.R#L83-L94
Ideally those would have the same output, but they clearly do not right now.
I've encountered two other issues for jsonlite that could potentially cause problems. One of them is easy to work around (and I've already added the workaround in shiny): jeroen/jsonlite#71. The other one is harder to work around: jeroen/jsonlite#76. But neither of them seem to be at issue here.
I think the difference is in how RJSONIO
and jsonlite
handle objects wrapped with I()
.
The htmlwidgets code uses I()
for evals
. It's something like this:
str(I("options.color.colorScale"))
# Class 'AsIs' chr "options.color.colorScale"
Here's how the two packages convert this differently:
RJSONIO:::toJSON(list(evals = I("options.color.colorScale")))
# [1] "{\n \"evals\": [ \"options.color.colorScale\" ] \n}"
# This wraps jsonlite::toJSON with various extra args
shiny:::toJSON(list(evals = I("options.color.colorScale")))
# {"evals":"options.color.colorScale"}
Without the I()
, here's what they give
RJSONIO:::toJSON(list(evals = "options.color.colorScale"))
# [1] "{\n \"evals\": \"options.color.colorScale\" \n}"
shiny:::toJSON(list(evals = "options.color.colorScale"))
# {"evals":"options.color.colorScale"}
The question is, which behavior makes more sense? I don't see any reason why I()
should result in an extra []
, but I could be missing something.
In htmlwidgets, we need auto_unbox = FALSE in this particular case. The reason is here: https://github.com/ramnathv/htmlwidgets/blob/921ba807c85404f6b8b16a8adb15c4401cf1a922/inst/www/htmlwidgets.js#L379-L381 We need data.evals
to be always an array. Hence I used I()
to tell RJSONIO that this vector must be converted to an array.
This issue should be easy to solve in htmlwidgets, and I'll send a PR shortly.
I don't see any reason why
I()
should result in an extra[]
, but I could be missing something.
That's simply how you opt out of auto-unboxing on a per-vector basis in RJSONIO. From ?RJSONIO::toJSON
:
Objects of class AsIs in R, i.e. that are enclosed in a call to I() are treated as containers even if
they are of length 1. This allows callers to indicate the desired representation of an R "scalar" as an
array of length 1 in JSON
OK, that sounds like something that should be added to jsonlite's auto_unbox
behavior. I'll file an issue with jsonlite about that.
Update: The dev version of jsonlite now has the same behavior that RJSONIO does. In other words, the I()
tells it to keep []
, even around single-element vectors.