Just for the record: attached the wrong diff, so attaching the correct one to this posting. Sorry for the confusion.
---rony On 13.11.2019 15:14, Rony G. Flatscher wrote: > Hmm, not getting any feedback so far, so wondering if there are currently any > Java developers who > take advantage of the ability of FXMLLoader to have FXML controllers > implemented in any of the Java > javax.script languages? > > For those, who use scripting languages for FXML controllers the request that > FXMLLoader adds both > entries, ScriptEngine.FILENAME (for debugging, logging) and ScriptEngine.ARGV > () (for making the > event object available directly as an argument) into the engine Bindings, > should be quite helpful > while developing and running the scripts. > > [Personally I am using the scripting engine ooRexx successfully for teaching > oo programming from > scratch to JavaFX in a single semester (four hour lecture, eight ECTS) at a > Business Administration > university. So these two missing features in the current FXMLLoader support > for FXML controllers > would help tremendously, especially in case of coding errors as currently it > is not clear from which > file the script that has an error comes from, making it extremely cumbersome > and time consuming in > JavaFX applications that use multiple and complex FXML files.] > > Therefore I would kindly ask interested committers for mentoring the proposed > changes. Enclosed > please find a simpler version of the patch that adds these missing features > to the ENGINE_SCOPE > Bindings in the three locations where ScriptEngine.eval() gets invoked (it ). > > To comment this simple patch, maybe I should add a few remarks such that the > context becomes clear: > > * invoking a script via ScriptEngine.eval() will always be accompanied with > a ScriptContext that > usually maintains two Bindings (Maps): > > o one, GLOBAL_SCOPE Bindings, for global entries (used e.g. for putting > the FXML elements that > have an fx:id attribute defined, such that scripts can get access to > them, independent of a > particular ScriptEngine) which can also be used for sharing values > among different script > invocations, > > o one, ENGINE_SCOPE Bindings, usually used for individual invocations. > > * while a FXML file gets processed sequentially by the FXMLLoader elements > in the form of > "<fx:script source="someScript.ext" />" will cause invoking the > ScriptEngine.eval(Reader): this > patch fetches the ENGINE_SCOPE Bindings and puts the value > "someScript.ext" with the key > ScriptEngine.FILENAME into it (cf. "@@ -1558,6 +1558,9 @@ public class > FXMLLoader" and "@@ > -1582,6 +1585,8 @@ public class FXMLLoader" in the patch), > > * if an event handler gets defined (e.g. with the event attribute > "<fx:button ... > onAction="someScript">") the FXMLLoader creates a ScriptEventHandler and > stores "someScript" and > the ScriptEngine for executing that script whenever the event fires. > > o When an event fires, the current implementation creates a copy of the > current ENGINE_SCOPE > Bindings from the ScriptEngine's ScriptContext, adds its entries to > it after saving the > entry "event" with the ActionEvent object in it. It then changes the > ScriptEngine's current > ScriptContext such that it now uses the new copy of the Bindings as > its ENGINE_SCOPE > Bindings, runs the script using eval() and then restores the > ScriptContext ENGINE_SCOPE > Bindings. > > o The supplied patch (cf. "@@ -1675,30 +1680,28 @@ public class > FXMLLoader") instead will > create a copy of the ENGINE_SCOPE Bindings only once at creation time > (and puts the > appropriate ScriptEngine.FILENAME into it using the name of the FXML > file that defines the > event script attribute) and will reuse that Bindings each time the > handler gets invoked, > after putting the actual "event" object and the respective > ScriptEngine.ARGV entry into it. > Using ScriptEngine.eval(String,Bindings) will use the supplied > Bindings as the ENGINE_SCOPE > Bindings for this invocation only, such that no restore is necessary > upon return. > > As only entries get added to the engine Bindings that have not been used by > FXMLLoader this simple > patch should not affect existing scripts. The patch has been tested and works. > > Maybe it helps the cause for applying this patch, if I point out that I have > been active in a number > of opensource projects, including Apache's BSF which led to my participation > as an expert in JSR-223 > which originally defined the javax.script framework introduced with Java 6 > (also authored a complete > ScriptEngine implementation with both, the javax.script.Compilable and the > javax.script.Invocable > interfaces). > > So looking for interested committers who would be willing to mentor this > patch. Please advise. > > ---rony > > > > On 06.11.2019 16:05, Rony G. Flatscher wrote: >> 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/gradlew b/gradlew old mode 100644 new mode 100755 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..1d56ca0614 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,9 @@ public class FXMLLoader { location = new URL(FXMLLoader.this.location, source); } + Bindings engineBindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + engineBindings.put(engine.FILENAME, location.getPath()); + InputStreamReader scriptReader = null; try { scriptReader = new InputStreamReader(location.openStream(), charset); @@ -1582,6 +1585,8 @@ public class FXMLLoader { if (value != null && !staticLoad) { // Evaluate the script try { + Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); + engineBindings.put(scriptEngine.FILENAME, location.getPath()); scriptEngine.eval((String)value); } catch (ScriptException exception) { System.err.println(exception.getMessage()); @@ -1675,30 +1680,29 @@ public class FXMLLoader { private static class ScriptEventHandler implements EventHandler<Event> { public final String script; public final ScriptEngine scriptEngine; + public final Bindings engineBindings; public ScriptEventHandler(String script, ScriptEngine scriptEngine) { this.script = script; this.scriptEngine = scriptEngine; + engineBindings = scriptEngine.createBindings(); + engineBindings.putAll(scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE)); + URL location=(URL) scriptEngine.getBindings(ScriptContext.GLOBAL_SCOPE).get(LOCATION_KEY); + engineBindings.put(scriptEngine.FILENAME, location.getPath()); } @Override public void handle(Event event) { - // Don't pollute the page namespace with values defined in the script - Bindings engineBindings = scriptEngine.getBindings(ScriptContext.ENGINE_SCOPE); - Bindings localBindings = scriptEngine.createBindings(); - localBindings.put(EVENT_KEY, event); - localBindings.putAll(engineBindings); - scriptEngine.setBindings(localBindings, ScriptContext.ENGINE_SCOPE); + // Don't pollute the page namespace with values defined in the script use initial engineBindings + engineBindings.put(EVENT_KEY, event); + engineBindings.put(scriptEngine.ARGV, new Object[]{event}); // Execute the script try { - scriptEngine.eval(script); + scriptEngine.eval(script,engineBindings); } catch (ScriptException exception){ throw new RuntimeException(exception); } - - // Restore the original bindings - scriptEngine.setBindings(engineBindings, ScriptContext.ENGINE_SCOPE); } }