Open
Description
reactable
has an underappreciated WidgetContainer
that handles htmlwidgets
(see lines ). I think vueR
should have a similar structure but without the tags
for data and options. Here is a very rough draft example that needs significant improvement, iteration, and testing.
library(htmltools)
library(htmlwidgets)
library(shiny)
library(vueR)
library(listviewer)
library(plotly)
# handle non-standard behaviors by some widgets
get_widget_data <- function(widget) {
as.tags(widget)[[2]]$children[[1]]
}
p <- plot_ly(palmerpenguins::penguins, x = ~bill_length_mm, y = ~body_mass_g)
tl <- tagList(
crosstalk::crosstalkLibs(), # necessary for g2
vueR::html_dependency_vue(minified = FALSE),
htmlDependency(
"htmlwidgets",
packageVersion("htmlwidgets"),
src = system.file("www", package = "htmlwidgets"),
script = "htmlwidgets.js"
),
p$dependencies, # this is far from ideal but plotly works differently; in most cases do not need to add since *Output handles
tags$div(
tags$button("update data", onclick = "updateData()")
),
tags$div(
id = "app",
tag('html-widget', list(
jsoneditOutput("je"),
`:x` = 'x',
`name` = 'jsonedit' # ideally we find a way to avoid this
)),
tag('html-widget', list(
plotlyOutput("pl"),
`:x` = 'x',
`name` = 'plotly' # ideally we find a way to avoid this
))
),
tags$script(HTML(
sprintf("
Vue.component(
'html-widget',
{
props: ['x', 'name'],
template: '<div><slot></slot></div>',
methods: {
// Copied from HTMLWidgets code
// Implement a vague facsimilie of jQuery's data method
elementData: function(el, name, value) {
if (arguments.length == 2) {
return el['htmlwidget_data_' + name];
} else if (arguments.length == 3) {
el['htmlwidget_data_' + name] = value;
return el;
} else {
throw new Error('Wrong number of arguments for elementData: ' +
arguments.length);
}
},
updateWidget: function() {
var component = this;
// use HTMLWidgets.widgets to give us a list of available htmlwidget bindings
var widgets = HTMLWidgets.widgets;
// assume there might be lots, so filter for the one we want
// in this case, we want jsonedit
var widget = widgets.filter(function(widget){
return widget.name === component.name
})[0];
// get our htmlwidget DOM element
var el = this.$el.querySelector('.html-widget');
var instance = this.elementData(el, 'init_result')
widget.renderValue(
el,
this.x,
instance
);
}
},
mounted: function() {
if(typeof(this.x) === 'undefined' || this.x === null) { return }
var component = this;
// use HTMLWidgets.widgets to give us a list of available htmlwiget bindings
var widgets = HTMLWidgets.widgets;
// assume there might be lots, so filter for the one we want
// in this case, we want jsonedit
var widget = widgets.filter(function(widget){
return widget.name === component.name
})[0];
// get our htmlwidget DOM element
var el = this.$el.querySelector('.html-widget');
// get our htmlwidget instance with initialize
var instance = widget.initialize(el);
this.elementData(el, 'init_result', instance);
widget.renderValue(
el,
this.x,
instance
);
},
// updated not working since does not watch deep
// but if the expectation is that data and options are replaced completely
// then updated will trigger
updated: function() {
this.updateWidget()
},
watch: {
x: {
handler: function() {console.log('updating');this.updateWidget()},
deep: true
}
}
}
)
var app = new Vue({
el: '#app',
data: () => (%s)
})
function updateData() {
app.x.data[0].y = app.x.data[0].y.map(d => Math.random())
}
",
get_widget_data(p)
)
))
)
browsable(tl)