Using a script engine (javax.script.ScriptEngine) for implementing a FXML controller there are two important information missing in the ScriptContext.ENGINE_SCOPE Bindings supplied to the script used to eval() the script code:
* ScriptEngine.FILENAME o This value denotes the file name from where the script code was fetched that is being eval()'d. o When debugging script controllers in a complex JavaFX application it is mandatory to know the file name the script code was taken from (as such scripts could be called/run from different FXML files). Also, in the case of script runtime errors, usually the file name is given by the script engine where the error has occurred to ease debugging, such that it is important to really supply the filename. + Note: the 'location'-URL in ScriptContext.GLOBAL_SCOPE refers the FXML file, not to the file that hosts the script that gets run if using the "<fx:script" element where the "source" attribute denotes the name of the script file. o General solution: supply the appropriate ScriptEngine.FILENAME entry to the ScriptContext.ENGINE_SCOPE Bindings. * ScriptEngine.ARGV o This value denotes the arguments that get passed to the script from Java in form of a Java Array of type Object. o When defining event handlers in FXML files in script code the script does not get the appropriate argument. Rather the script programmer needs to access the ScriptContext.ENGINE_SCOPE and fetch the entry named "event" from there. Some script engines may make the entries in the Bindings implicitly available to the scripts, however this cannot be expected by default. However, a ScriptEngine.ARGV entry must be supplied to the script by the script engine implementor, such that a script coder gets the event object argument in the script language's manner. o General solution: supply the appropriate ScriptEngine.ARGV Object array to the ScriptContext.ENGINE_SCOPE Bindings. With these two changes not only writing controller scripts would be eased, it also would instrumentate ScriptContext.ENGINE_SCOPE Bindings the way it was intended by JSR-223. Enclosed please find a tested diff for FXMLLoader.java from the current OpenJavaFX Master (version 14) that implements both, ScriptEngine.FILENAME entries for all script invocations and in the case of a script event handler the appropriate ScriptEngine.ARGV entry gets supplied, allowing the script to fetch the event object directly as an argument. As I have signed the OCA the code (in form of a git diff) can be directly applied to FXMLLoader.java. If you need the patch in a different form, then please advise. ---rony
diff --git a/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java b/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java index 7f3d2f3083..eab4541659 100644 --- a/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java +++ b/modules/javafx.fxml/src/main/java/javafx/fxml/FXMLLoader.java @@ -1558,6 +1558,12 @@ public class FXMLLoader { location = new URL(FXMLLoader.this.location, source); } + Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + Bindings localBindings = engine.createBindings(); + localBindings.put(engine.FILENAME, location.getPath()); + localBindings.putAll(engineBindings); + scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE); + InputStreamReader scriptReader = null; try { scriptReader = new InputStreamReader(location.openStream(), charset); @@ -1569,6 +1575,9 @@ public class FXMLLoader { scriptReader.close(); } } + + // Restore the original bindings + engine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE); } catch (IOException exception) { throw constructLoadException(exception); } @@ -1582,7 +1591,16 @@ public class FXMLLoader { if (value != null && !staticLoad) { // Evaluate the script try { + Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); + Bindings localBindings = scriptEngine.createBindings(); + localBindings.put(scriptEngine.FILENAME, location.getPath()); + localBindings.putAll(engineBindings); + scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE); + scriptEngine.eval((String)value); + + // Restore the original bindings + scriptEngine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE); } catch (ScriptException exception) { System.err.println(exception.getMessage()); } @@ -1687,6 +1705,9 @@ public class FXMLLoader { Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); Bindings localBindings = scriptEngine.createBindings(); localBindings.put(EVENT_KEY, event); + localBindings.put(scriptEngine.ARGV, new Object[]{event}); + URL location=(URL) scriptEngine.getBindings(ScriptContext.GLOBAL_SCOPE).get(LOCATION_KEY); + localBindings.put(scriptEngine.FILENAME, location.getPath()); localBindings.putAll(engineBindings); scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE);