I'm writing an rgl
widget within the htmlwidgets framework so that rgl
scenes can be used for output in Shiny apps. Things are basically working (though it's still rough around the edges; see the rglwidget
package on http://R-forge.r-project.org ), but it's not nearly as responsive as the native Javascript controls that are already in rgl
.
I suspect the problem is the round trip to the server.
Sometimes this will be unavoidable: if you want to make big changes to a scene, you may want to do a lot of calculations in R. But in other cases (the ones covered by the native controls), there's no need for R to be involved, everything can be done in Javascript.
I don't want to duplicate all the work that has gone into writing the Shiny input controls, but I'd like to use them. So my question is:
Is there a way to tell a Shiny input to call a Javascript function when it is changed, not to send its value to the server to be used in a Shiny output?
The answer to my question is "Yes"! It's actually fairly straightforward, at least if the control uses the htmlwidgets
framework.
Warning: I'm not very experienced in Javascript, so this may not be very good Javascript style. Please let me know if so, and I'll improve it.
Here's the idea: If I have a Shiny sliderInput()
control with inputId = "foo"
, then my own Javascript code can get it using window["foo"]
, and can set an "onchange" event handler on it. When that event handler is triggered, I can read the "value" property, and send it to my controls.
If I don't use the reactive inputs from the slider, I don't get the delay from going to the server.
Here's my current renderValue function for the widget:
renderValue: function(el, x, instance) {
var applyVals = function() {
/* We might be running before the scene exists. If so, it
will have to apply our initial value. */
var scene = window[x.sceneId].rglinstance;
if (typeof scene !== "undefined") {
scene.applyControls(x.controls);
instance.initialized = true;
} else {
instance.controls = x.controls;
instance.initialized = false;
}
};
el.rglcontroller = instance;
if (x.respondTo !== null) {
var control = window[x.respondTo];
if (typeof control !== "undefined") {
var self = this, i, oldhandler = control.onchange;
control.onchange = function() {
for (i=0; i<x.controls.length; i++) {
x.controls[i].value = control.value;
}
if (oldhandler !== null)
oldhandler.call(this);
applyVals();
};
control.onchange();
}
}
applyVals();
},