With people porting HTML to Nashorn + JavaFX more frequently, I often get asked the question "how do you port functions like setInterval and setTimeout" (setInterval is used to have a function repeated at regular intervals, where setTimeout is used to have a function delayed.) The following code duplicates the functionality of the each of the setInterval family of functions, when used in a JavaFX application.
var Platform = Java.type("javafx.application.Platform");
var Timer = Java.type("java.util.Timer");
function setInterval(func, milliseconds) {
// New timer, run as daemon so the application can quit
var timer = new Timer("setInterval", true);
timer.schedule(function() Platform.runLater(func), milliseconds, milliseconds);
return timer;
}
function clearInterval(timer) {
timer.cancel();
}
function setTimeout(func, milliseconds) {
// New timer, run as daemon so the application can quit
var timer = new Timer("setTimeout", true);
timer.schedule(function() Platform.runLater(func), milliseconds);
return timer;
}
function clearTimeout(timer) {
timer.cancel();
} <Update March 21, 2014>
I was requested by a reader to implement setInterval, setTimeout and setImmediate with the same arguments as defined in DOM.
var Platform = Java.type("javafx.application.Platform");
var Timer = Java.type("java.util.Timer");
function setTimerRequest(handler, delay, interval, args) {
handler = handler || function() {};
delay = delay || 0;
interval = interval || 0;
var applyHandler = function() handler.apply(this, args);
var runLater = function() Platform.runLater(applyHandler);
var timer = new Timer("setTimerRequest", true);
if (interval > 0) {
timer.schedule(runLater, delay, interval);
} else {
timer.schedule(runLater, delay);
}
return timer;
}
function clearTimerRequest(timer) {
timer.cancel();
}
function setInterval() {
var args = Array.prototype.slice.call(arguments);
var handler = args.shift();
var ms = args.shift();
return setTimerRequest(handler, ms, ms, args);
}
function clearInterval(timer) {
clearTimerRequest(timer);
}
function setTimeout() {
var args = Array.prototype.slice.call(arguments);
var handler = args.shift();
var ms = args.shift();
return setTimerRequest(handler, ms, 0, args);
}
function clearTimeout(timer) {
clearTimerRequest(timer);
}
function setImmediate() {
var args = Array.prototype.slice.call(arguments);
var handler = args.shift();
return setTimerRequest(handler, 0, 0, args);
}
function clearImmediate(timer) {
clearTimerRequest(timer);
}
Just wondering - why this
Platform.runLater(function() { func(); })
instead of this?
Platform.runLater(func)
My mistake. The original code was trying to create a closure to hold func.
Wrapping the code in the setInterval function is sufficient. Updated the code
above.
This is a nice DOM event simulator. For those of us who are very interested in using Nashorn but also need a browser environment -- do you have any suggestions?
Nothing specific to offer. There is a copy of env.js in the repo that works in Nashorn. Depending on your application, DOM to FX translates (manually) quite well. Having a complete DOM solution for Nashorn would take some time, but it is on the table.
FYI above snippet is not ECMAScript standard. The setInterval should starts after `milliseconds` plus both setTimeout and setInterval, as well as setImmediate, accept 0, 1 or N arguments after the optional delay.
Last, but not least, the moment you introduce `javafx.application.Platform` in the game `jjs` won't be a ble to run it anymore with or without `-fx` flag
Clearly, setInterval is wrong (corrected), but was merely to demonstrate how you would duplicate the functionality of DOM setInterval. Without an event loop, either supported by DOM or FX, setInterval, setTimer, setImmediate would have little meaning.
Hi, any idea how I can pass a method reference from Java to Nashorn via invokeFunction? For example:
//JS:
function foo(callback) {
callback('bar');
}
//Java
...
invocable.invokeFunction('foo', new CallBackClass()??)
...
With everything I've tried, I get an error about method signature not matching, but I can't figure out what signature it's trying to use.
What does CallBackClass look like?
The following example passes a method handle to invokeFunction - note that the cast is needed because invokeFunction accepts "Object"-s for arguments. javac can't figure out you're passing an @FunctionalInterface object without that cast to Consumer.
import javax.script.*;
import java.util.function.Consumer;
public class Main {
public static void main(String[] ar) throws Exception {
ScriptEngineManager m = new ScriptEngineManager();
ScriptEngine e = m.getEngineByName("nashorn");
e.eval("function foo(callback) { callback('bar') }");
((Invocable)e).invokeFunction("foo",
(Consumer<String>)System.out::println);
}
}
Thank you! CallbackClass implemented Callable. This is much nicer.
The first one is quite different in semantics to the JS functions, because the timer callbacks get invoked on a Timer thread instead of the JS event loop. This will get you some ugly multithreading problems.
Not sure which comment you are responding to, but Platform.runLater causes the task to run on the main thread.