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);
 

Reply via email to