Call JavaScript function from JavaFX before loading - javascript

I need to initialize some variables in JavaScript from JavaFX WebEngine before actually loading the html file. My .html file looks something like this:
<!DOCTYPE html>
<html>
<head>
<script type='text/javascript'>
var a;
function setA(aa) {
a = aa;
}
function f() {
<!-- ... some operation with var a-->
}
</script>
</head>
<body onload='f();'>
<!-- ... -->
</body>
</html>
And for the JavaFX part:
#Override
public void start(Stage primaryStage) throws InterruptedException {
WebView webview = new WebView();
WebEngine webengine=webview.getEngine();
// Set var a before webengine.load
webengine.load(getClass().getResource("hello.html").toString());
Pane p = new Pane();
p.getChildren().add(webview);
Scene scene = new Scene(p);
primaryStage.setScene(scene);
primaryStage.show();
}
I tried not putting onload='f();' in body tag of the .html, and then I tried calling JavaScript functions one by one from Java like this:
webengine.load(getClass().getResource("hello.html").toString());
webengine.executeScript("setA(123)");
webengine.executeScript("f()");
but this doesn't work (I get ReferenceError: Can't find variable setA/f ).
Any potential solutions?

So you want to execute some js stuff before the "browser" start processing the html content...
Short answer... you can't really do that.
Long answer... you can actually do that, but you'll need to either use a locally modified version of the jdk in which you modify hidden components such as WebPage which is a compoment used by WebEngine ... or use reflection to bypass runtime access policies... but even so, it opens up a can of worms and you'll have no guarantee it will work the way you expect it.

Related

'ggbApplet is not defined' error in javascript

I have a web page which include a geogebra applet. On loading the page I would like to modify certain elements in the applet, depending on user input. I tested the following code
<script type="text/javascript">
var parameters = {
"width":600,"height":450,
"filename":"trois_ensembles.ggb"};
var applet = new GGBApplet('5.0', parameters);
window.onload = function() {
applet.inject('applet_container');
ggbApplet.setVisible('A', false);
}
</script>
But the line
ggbApplet.setVisible('A', false);
produces an error in the console
ReferenceError: ggbApplet is not defined
When ggbApplet is referenced elsewhere, as in
<button onclick="ggbApplet.setVisible('A', true)">Set A </button>
everything works as expected.
I don't understand the scope of ggbApplet. Help appreciated.
Checking Global JavaScript section of Scripting page of Geogebra's wiki may be useful.
Cheers.
See this section:
Note: Using any ggbApplet methods in Global JavaScript outside of ggbOnInit will not work as intended since they will be called before the construction is loaded.

JavaFX WebView - Communicating between Java and Javascript after page redirection

I'm developing a JavaFX WebView application to let users navigate through an existing third-party website.
The pages on the site rely on $(document).ready() style functions in order to work properly and my Java Bridge object should be visible to the JavaScript side by then.
While my code works as expected for the first page loaded, for any other page the user navigates to the Bridge object doesn't seem to get registered until way after the JavaScript of the page has finished executing, likely throwing a bunch of 'undefined' errors.
Here's a quick project I put together demonstrating what I'm trying to do:
WebViewTest.java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;
public class WebViewTest extends Application {
#Override
public void start(Stage stage) {
try {
WebView web = new WebView();
WebEngine engine = web.getEngine();
Bridge bridge = new Bridge();
engine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
switch (newState) {
//Doing this in any state other than SUCCEEDED doesn't seem to do anything.
case SUCCEEDED:
JSObject jsobj = (JSObject) engine.executeScript("window");
jsobj.setMember("JavaBridge", bridge);
break;
}
});
StackPane root = new StackPane();
root.getChildren().add(web);
Scene scene = new Scene(root, 800, 600);
stage.setScene(scene);
stage.show();
//Adding these two lines gets the very first page loaded to work as expected.
JSObject jsobj = (JSObject) engine.executeScript("window");
jsobj.setMember("JavaBridge", bridge);
engine.load("http://localhost/JavaFXTest/first.html");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
Bridge.java
public class Bridge {
public void print(String msg) {
System.out.println("Invoked from JavaScript: " + msg);
}
}
first.html
<html>
<head>
<script src="jquery-1.12.4.min.js"></script>
</head>
<body>
<script>
JavaBridge.print('FIRST');
$(document).ready(function(){
JavaBridge.print('FIRST READY');
});
</script>
<h1>first</h1>
Second
<button onclick="JavaBridge.print('FIRST CLICK');">Click!</button>
</body>
</html>
second.html
<html>
<head>
<script src="jquery-1.12.4.min.js"></script>
</head>
<body>
<script>
JavaBridge.print('SECOND');
$(document).ready(function(){
JavaBridge.print('SECOND READY');
});
</script>
<h1>second</h1>
First
<button onclick="JavaBridge.print('SECOND CLICK');">Click!</button>
</body>
</html>
Running the application initially outputs, as desired:
Invoked from JavaScript: FIRST
Invoked from JavaScript: FIRST READY
However, after clicking the link to go to the second page, neither of those lines are printed, yet clicking on the button works as intended:
Invoked from JavaScript: SECOND CLICK
And if we click the first link to go back to the first page, the lines are not printed either, but clicking works.
The only workaround I've been able to come up with so far is completely discarding my current WebView object and creating a new one anytime a location listener fires, as subsequent calls to engine.load() don't seem to help either. Nevertheless, doing that kinda breaks things if there are sessions or post parameters involved...
I have been struggling with this for a few days and I'm pretty much stuck now, so if anyone can point out if I'm missing something, I'd appreciate it.

webEngine.executeScript(); Throwing Exception

I'm writing a JavaFX App, which Interacts with the JavaScript, Using WebView and WebEngine (.executeScript() Method).
Here, I have this part of code from Medow.java, which loads map.html(Contains JavaScript Code), And This Code Works Pretty well:
add_button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent ea5) {
// webEngine.executeScript("document.fun();"); // For Drawing Shapes
if (add == false) {
webEngine.executeScript("document.fun();"); // For Drawing Shapes
add = true;
}
// }
else {
webEngine.executeScript("document.reSet();"); // To remove Drawing Shapes
add = false;
}
}
});
In Here
webEngine.executeScript();
Is Invoking Appropriate JavaScript function's
But Now, I want my Java Code to Invoke Some JS function, when the Program Starts, So I'm directly writing :
webEngine.executeScript("document.draw();");
right Under/after the code, which loads the map.html file.
So, now as Both of the
webEngine.execute("document.fun();"); and webEngine.executeScript("document.draw();"); are nearly similar, I cannot Understand what Difference does, it makes to be inside the <button>.setOnAction block and to be outside it, Because both WebEngine and webView are declared as Global Variables.
cannot invoke document.draw() function using HTML's onLoad options, because i need to pass some Values To function draw from java.
The Exception Generated is :
netscape.javascript.JSException: TypeError: undefined is not a function (evaluating 'document.draw()')
how can i make this work? thank you
While Continuously trying to figure out whats the cause, I Discovered That the HTMLDocument Object, created using webEngine.load(), is for some reason visible only inside the handle method, And nowhere else, even though its been defined outside.
What happens here is that you want to call a JavaScript function before the content is loaded completely. Therefore the function draw is not defined. So the only way is to wait until the page is loaded. There are two ways that might prove useful:
Add a changelistener on the state and execute the JavaScript once the loading has succeeded:
String htmlURL = ...
webView.getEngine().load(htmlURL);
webView.getEngine().stateProperty().addListener(new ChangeListener<Worker.State>() {
#Override
public void changed(ObservableValue<? extends Worker.State> ov, Worker.State t, Worker.State t1) {
if (t1 == Worker.State.SUCCEEDED) {
// this will be run as soon as WebView is initialized.
webView.getEngine().executeScript("document.draw()");
}
}
});
The other way is more of a solution within JavaScript. You first have to register a bridge between Java and your html page (has to be done in the SUCCEEDED state change as well, see WebView callback from Javascript):
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", this);
Now this JavaObject is referenced in your JavaScript. Let's say that you have a method on the class that is of the above type of this:
public void executeOnPageLoaded() {
...
}
Then you can call this method from Javascript. If you are using jQuery it could look like this:
$( document ).ready(function() {
console.log( "ready!" );
app.executeOnPageLoaded();
});
This second approach is more complex but in the long run may give you more flexibility.
When you start working with JavaScript in the WebView is is a good idea to have Firebug lite in there as well, so investigate what is happening but mainly to have a means to seed the console output of JavaScript. See Java FX application onload event

GWT Chart.js implementation throws 'unable to get property 'insertBefore' of undefined or null reference

I'm trying to integrate the Chart.js charts (http://www.chartjs.org) in a current GWT project. I have the project recognizing the javaScript but when I attempt to instantiate a Chart, my browser throws the following JavaScript error:
Unable to get property 'insertBefore' of undefined or null reference
Using the following GWT code snippet that seems to be the root of the problem:
Public class PieChart implements IsWidget
{
Canvas c;
public JavaScriptObject jsChart;
public PieChart()
{
c = Canvas.createIfSupported();
if (c.getContext2d().getCanvas().getParentNode() == null)
{
//Debug message here
}
jsChart = init(c.getContext2d());
}
private native JavaScriptObject init(Context2d ctx)
/*-{
var data = [];
return new $wnd.Chart(ctx).Pie(data); //<-- *Explodes here*
}-*/;
#Override
public Widget asWidget() { return c; }
}
An instance of this PieChart class is created and added to a VerticalPanel during client creation.
The null debugging check condition reveals that the parentNode is indeed null and digging into the JavaScript reveals a call to 'parentNode.insertBefore' during the creation of the Chart object - so naturally it fails.
A null check of the Canvas c reveals a valid object, yet no parentNode so to my mind, there is either something wrong with my html document such that the Canvas is the topmost object or there is a configuration issue with my project overall that causes the call to the Canvas creation method to return a poorly constructed instance.
Is there a way to artificially bury my created Canvas one level down so that the parentNode won't be null (Or something like that)? Or am I missing a step somewhere?
Here's the HTML of my page:
<!doctype html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link type="text/css" rel="sytlesheet" href="viewer.css">
<title>Viewer</title>
<script type="text/javascript" language="javascript" src="viewer/moment.js"></script>
<script type="text/javascript" language="javascript" src="viewer/Chart.js"></script>
<script type="text/javascript" language="javascript" src="viewer/viewer.nocache.js"></script>
</head>
<body>
<noscript>
...
</noscript>
</body>
</html>
The project cannot be hosted on public internet, hence the need for an offline solution like Chart.js
I'm also open to alternative offline charting packages that play well with GWT. However, the alternative must be licensed as open source or otherwise freely distributable commercially worldwide for ITAR export compliance.
Thanks folks!
private native JavaScriptObject init(Context2d ctx)
/*-{
var data = [];
return $wnd.Chart(e).Pie(data); //<-- *Explodes here*
}-*/;
What is e? Is it allowed to be undefined at this point when you pass it to the chart?
From just a very quick glance at the linked project, you should be creating that Chart with new, and you should be passing the canvas Context2d instance ctx into it instead of this unknown e.
It is also very likely due to how canvas works that this will not behave correctly if your canvas is not attached to the dom, and your PieChart constructor doesn't attach it before it invokes init. If that is a requirement, you likely need to not invoke init until the widget's attach event goes off or onAttach is called, but you should learn more about how to wrap this project.
There are GWT compatible chart libraries out there, including Lienzo, GXT, and Highcharts. Full disclosure, I worked with Sencha building GXT for several years, and so have an obvious preference there.

Override current Javascript functions with Android Webview

I'm porting my website to Android, and using an Webview to show the content to user. There are a lot of Javascript functions in my website, and I want to intercept them. I already seen a "solution" here.
However, I think there should be a more proper way, using Javascript Interface:
this.webView.addJavascriptInterface(this.webJavascriptInterface, "Android");
This way, I have to modify my website to call both myFunction() and Android.myFunction(). I tried to leave a blank String in the interface:
this.webView.addJavascriptInterface(this.webJavascriptInterface, "");
but the result was as I guess, it couldn't work. Is there a way to override current Javascript functions in Webview?
// android
webView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
webView.loadUrl("javascript:function inputClick(val){native.abcd(val);}"); // override js function
}
});
webView.loadUrl("file:///android_asset/test.html");
webView.addJavascriptInterface(new Object() {
#JavascriptInterface
public void abcd(int val) {
Log.e(TAG, "#js abcd" + val);
}
}, "native");
<!-- html -->
<input type="button" onclick="inputClick(2)" value="button">
== add some explain at Sep 07,2015
because javascript function could be override, so you can override javascript function while page finished.
if not, normal implement maybe like this:
// js code
function inputClick(val) {
native.abcd(val); // native and abcd defined in WebView method addJavascriptInterface
}
but this normal implement is seems not work in iOS(Object-C), and must edit HTML page.
is just move the java-javascript bridge code from HTML to Java. (see method onPageFinished in example code)
You can do this by injecting a new javascript that redefines old function, basically you can override existing function with this.
For example, to override window.print() function, I use this
webView.loadUrl("javascript:window.print = function() {Android.printPage();}")

Categories

Resources