1

I have a problem. I have two classes in same javafx package. A single html file with javascript at the head section, a java class(extending Application). Now the problem is when i tried to click the button after the page is displayed in the javafx webview, nothing is updated in the webView. Below is the code for the two file. Please i need to know why it isn't working. i have been debugging this problem since 8hrs now, no success. thanks in advance. java class

    import java.net.MalformedURLException;
import java.net.URISyntaxException;
import javafx.application.Application;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.layout.VBox;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

public class JavaFXApplication25 extends Application {
// inner class
public class Adder
{
    public double add(double n, double m)
    {
        return n + m;
    }
}

@Override
public void start(Stage primaryStage) throws URISyntaxException, MalformedURLException {
    WebView w = new WebView();
    WebEngine e = w.getEngine();
    e.setJavaScriptEnabled(true);
    e.load(this.getClass().getResource("tester.html").toURI().toURL().toExternalForm());
    Scene scene = new Scene(new VBox(w));
    primaryStage.setScene(scene);
    primaryStage.show();
   
    // make javascript aware of java object
    e.getLoadWorker().stateProperty().addListener(
    (p, o, n) ->{
        if(n == Worker.State.SUCCEEDED){
           JSObject b = (JSObject) e.executeScript("window");
            b.setMember("adder", new Adder());
        }
    }
    );
    
    
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}

}

The html file

<html>
<head>
    <title>TODO supply a title</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript">
        function addNum(){
            
        var n1 = Number(document.getElementById('num1').value);
        var n2 = Number(document.getElementById('num2').value);
        var n3 = adder.add(n1, n2); 
        document.getElementById('r').innerHTML = n3;
        }
    </script>
</head>
<body>
    <input type="text" id="num1" />
    <input type="text" id="num2" />
    <p> <span id="r"> </span></p>
    <button onclick="addNum()" >Add</button>
</body>

The point is that the programs runs and displays the page, but on pressing the button, nothing is updated on the page I even tried to make the upcall before loading the html page, yet, no success. Please someone should help check the bug in the code. Thanks once again.

Now below is the output after been run. It shows nothing even after when the Add button is clicked several times! No error message on the standard console, nothing nothing! output

Lucky
  • 21
  • 3
  • You should definitely be adding the listener before loading the HTML. Your javascript doesn't seem to refer to the `adder` object at all, so isn't the issue in the HTML/Javascript? – James_D Feb 09 '21 at 02:15

2 Answers2

0
if(n == Worker.State.SUCCEEDED){
    JSObject b = (JSObject) e.executeScript("window");
    b.setMember("adder", new Adder());
}

Welcome to the execrable pain in the ass that is DOM event sync between JavaFX and DOM stuff.

The worker state "SUCCEEDED" does not necessarily mean that the DOM is loaded in the page.

The DOM is loaded at document.onLoad() ... and even so it's kind of a can of worms since not all content is loaded by that time (images and stuff). JQuery has a convenient JQuery(document).ready(function (e){ /*do whatever*/ }) routine you can use to trigger something that lets JavaFX know that it's ready to do stuff in the DOM (basically what you tried to accomplish with the code above [..]Worker.State.SUCCEEDED[..])

Essentially the sequence of events is something like:

1. JavaFX => Worker.state reaches SUCCEEDED STATE
2. DOM ====> Starts converting HTML into DOM components
3. ??? ====> This step is a complete mystery unbeknownst to even the savvy-est of skript kiddies. but i'm pretty sure this is where zuckerberg gets access to your webcam for a few frames. ... so beware!
4. DOM ====> Finishes doing its thing and fires off the onLoad() event which triggers the document.load() listener and associated function.
5. DOM ====> Officially inovkes the first JS function it comes across (not always the one you'd think it'd come across because JS is a magic programming language that doesn't give a shit about threads, or sequencing thereof, and anything goes when it comes JOs)
6. DOM+JS => Continues loading for some reason (i am not kidding)
7. DOM+JS => Some other weird flibbetlygibbletly bullshit done by the DOM and whatever javascript runs inside it ... if you're using timers... good luck.
8. DOM+JS => at this point, jQuery (if used) intercepts the .ready() event listener.
9. DOM+JS => believe me... stuff still isn't completely loaded. (because rampant JS scripts can add DOM components as they see fit and no .ready or .load event listener will ever be called.)

To add insult to injury, this sequence of events can take anywhere between 1ms and 1 minute to reach (8)... it's arbitrary as hell.

I wound up writing code which starts comparing node trees for changes in order to figure out when exactly to start messing with the DOM.... but i digress.

What you want is to make sure you initiate your JS-JavaFX communication protocol once the DOM is ready to run your custom javascript.

Put that shit in document.onLoad(), listen for it inside JavaFX, and then set the adder as you did so already... then hope for the best.

EverNight
  • 964
  • 7
  • 16
0

Thanks everyone...After a little research, i was able to come up with an extremely simple solution..Here it goes in steps:

  1. declare a private field (which is an object of the class that contains the public methods to be called from javascript) in the main class.

  2. instantiate the private field

  3. add the member to the web engine after it loads

  4. call the java method from the javascript. so here goes the updated code

         import java.net.MalformedURLException;
    

    import java.net.URISyntaxException; import javafx.application.Application; import javafx.concurrent.Worker; import javafx.scene.Scene; import javafx.scene.layout.VBox; import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; import netscape.javascript.JSObject;

    public class JavaFXApplication25 extends Application { // inner class public class Adder { public double add(double n, double m) { return n + m; } }

    // STEP 1: Now declare the private field (COMPULSORY)
    private Adder adder;

    @Override public void start(Stage primaryStage) throws URISyntaxException, MalformedURLException { WebView w = new WebView(); WebEngine e = w.getEngine(); e.setJavaScriptEnabled(true); e.load(this.getClass().getResource("tester.html").toURI().toURL().toExternalForm()); Scene scene = new Scene(new VBox(w)); primaryStage.setScene(scene); primaryStage.show();

    //STEP 2: instantiate the private js object from step1 adder = new Adder(); // make javascript aware of java object e.getLoadWorker().stateProperty().addListener( (p, o, n) ->{ if(n == Worker.State.SUCCEEDED){ JSObject b = (JSObject) e.executeScript("window"); b.setMember("adder", new Adder()); } } );

    }

    /**

    • @param args the command line arguments */ public static void main(String[] args) { launch(args); } }

The HTML code still remains the same as before.

//Now, i think there is a bug in this aspect of javafx with jdk 8 and above. I am specifically using jdk 8_126...version. It seems there is automatic destruction of the javascript handler class by the jvm. Fortunately, wrapping the js hanler as a private field in the main class seems to prevent this terrible occurrence.

Lucky
  • 21
  • 3