B4J Question Making Upcalls from JavaScript to JavaFX?

zyblux

Member
Licensed User
Hi Everyone,
I have recently been doing web development using B4j and found it extremely nice to use. I have no problems handling JS code from the server side and invoking JS from a desktop app using the WebView control (and JavaObject etc). But is it possible to invoke B4j code from JS running in the webview control? I found an example in the Oracle blog but not sure how to get it to work in B4j:

Any ideas?

Thanks

https://blogs.oracle.com/javafx/entry/communicating_between_javascript_and_javafx

Making Upcalls from JavaScript to JavaFX

Since we are talking about a two-way communication channel, what about making calls in the opposite direction: from a Web application into JavaFX? On the JavaFX side, you need to create an interface object (of any class) and make it known to JavaScript by calling JSObject.setMember(). Having performed this, you can call public methods from JavaScript and access public fields of that object.


The code below shows how to set up an interface object:


class Bridge {
public void exit() {
Platform.exit();
}
}
...
JSObject jsobj = (JSObject) webEngine.executeScript("window");
jsobj.setMember("java", new Bridge());

First we need a JSObject to attach our interface object to. The above code uses the JavaScript window object but any other object would work as well. Note that a cast is necessary. Then we create an interface object and add it as a new member of that JSObject. It becomes known to JavaScript under the name window.java, or just java, and its only method can be called from JavaScript as java.exit(). The upcall into Java is synchronous and occurs on the JavaFX Application thread. The following HTML code enables exiting the JavaFX application by clicking on a link:


<p>Click
<a href="" onclick="java.exit();">here</a>
to exit the application

Once you no longer need an interface object, you may want to call the JSObject.removeMember() method to make JavaScript "forget" it.
 

Erel

Administrator
Staff member
Licensed User
Which type of server are you implementing? WebSockets?
 

zyblux

Member
Licensed User
Sorry Erel, I don't think I explained myself very well. I'm trying to do a desktop/web client hybrid kind of thing.

I have a standard desktop app with a webview control on the form. I have no problem with websockets and jQuery etc from the server side - that whole mechanism is awesome. BUT what I'm trying to do is for certain web links to trigger code on the client side "outside" of the webview control. So clicking on a <button> tag in a web page will trigger an event in the deskop UI code not the ws/server, breaking out of the whole websocket stuff. I found examples of triggering JS in the webview control from UI code but I'd like to get it working both directions.

Cheers/Chris
 

scrat

Active Member
Licensed User
Hi

I have a similar problem.
I try to use leaflet.js with b4j and i must call sub from JS to B4j and from B4j to JS.

With B4a I use webviewextras and all works fine.

With b4j call JS function are ok but the problem is to call B4j function from JS.

I use this code.
B4J
B4X:
Dim win As JavaObject
win=WVO.RunMethodJO("getEngine",Null).RunMethodJO("executeScript", Array As String("window"))
win.RunMethodJO("setMember",Array As Object("b4j",MainForm))

sub test
log("from js")
end sub
JS
B4X:
  function userdragend() {
     b4j.test();
   }
Java code from Oracle

B4X:
public class JavaApplication {
public void exit() {
Platform.exit();
}
}
...
JSObject window = (JSObject) webEngine.executeScript("window");
window.setMember("app", new JavaApplication());
No error but test sub is never called.
How to pass B4J class to the webengine ?

Thank you
 

Daestrum

Well-Known Member
Licensed User
This example should get you started.
B4X:
Sub Process_Globals
  Private fx As JFX
  Private MainForm As Form
  Dim wv As WebView
End Sub

Sub AppStart (Form1 As Form, Args() As String)
  wv.Initialize("")
  MainForm = Form1
  MainForm.SetFormStyle("UNIFIED")
  'MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
  MainForm.Show
  Dim we As JavaObject
  we = asJO(wv).RunMethod("getEngine",Null)
  MainForm.RootPane.AddNode(wv,10,10,-1,-1)
  asJO(Me).RunMethod("setBridge",Array("b4j",we))
  we.RunMethod("loadContent",Array($"<p>Click
<a href="" onclick="b4j.test();">here</a>
to exit the application"$))
End Sub

Sub asJO(o As JavaObject) As JavaObject
  Return o
End Sub

Sub test()
  Log("test")
End Sub

#if java

import netscape.javascript.JSObject;
import javafx.scene.web.WebEngine;
import java.lang.RuntimeException;

public static class Bridge{
   public void test() {
       try{
             b4j.example.main._test();// redirect to b4j sub
            } catch (Exception e){
            }
    }
}

public static void setBridge(String s,WebEngine we){
   JSObject jsobj = (JSObject) we.executeScript("window");
   jsobj.setMember(s, new Bridge());
}
#end if
 

Daestrum

Well-Known Member
Licensed User
A better example attached.
This allows for calling the b4j sub by name instead of hard coding it.
The link routine takes just the name of the sub no params
The link2 routine takes the name plus a single parameter.
The routines in b4j are run on a thread (runlater...)


Note: link2 routine has some odd code as the javascript supplied an Integer and b4j wanted an int.
 

Attachments

Eme Fibonacci

Well-Known Member
Licensed User
I am using the example above.
Java inline:
PHP:
  public void CallSub2(String sub,Object arg) { // for call with  1 param
        boolean isInt = false;
        int ti = 0;
        try{
            Class<?> c = Class.forName("b4j.example.main");
            Class<?> ac = arg.getClass();
            if (arg instanceof java.lang.Integer){
                ac = int.class;
            }
            final Method m = c.getDeclaredMethod("_" + sub.toLowerCase(),ac);
            final Object dummy = null;
            final Object xarg = arg;
            Platform.runLater(new Runnable(){
                public void run(){
                    try{
                        m.invoke(dummy,xarg);
                    }catch (IllegalAccessException e){
                        System.out.println(e);
                    }catch (InvocationTargetException ite){
                    }
                }
            });
        } catch (Exception e){
            System.out.println(e);
        }
    }
In my javasctipt :
B4X:
<script>
function myFunction() {
b4j.CallSub2('Subname','Hello b4j');
}
</script>
Problem:
In B4J this works
B4X:
Sub Subname(s As String)
    Log(s)
End Sub
But This Don't work. Why?
B4X:
Sub Subname(s As Object)
    Log(s)
End Sub
 

Daestrum

Well-Known Member
Licensed User
Change
B4X:
            Class<?> ac = arg.getClass();
to
B4X:
            Class<?> ac = Object.class;
Then it will call the sub with an Object parameter is Sub fred(o As Object)
 

Osp

Member
Licensed User
..if the above does not work like for me with OpenJDK 11 for "no obvious reason" (did not get any upcalls to work, no logging of errors, nothing...), it seems like a static member variable referencing the bridge is needed to avoid problems with garbage collection and keep the Bridge available:
In the #If Java ... part add something like private static Bridge bridge and later use bridge = new Bridge(); and use this variable in the setMember("b4j", bridge) call.
This worked for me, upcalls working :)
 
Top