B4J Question Making Upcalls from JavaScript to JavaFX?

zyblux

Member
Licensed User
Longtime 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.
 

zyblux

Member
Licensed User
Longtime 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
 
Upvote 0

scrat

Active Member
Licensed User
Longtime 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
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime 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
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime 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

  • wvtest.zip
    1.5 KB · Views: 486
Upvote 0

Eme Fibonacci

Well-Known Member
Licensed User
Longtime 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
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
But This Don't work. Why?
1. you should have started a new thread for your issue.
2. A javascript can not send Objects like in java. the parameter from your javascript is a string.
 
Upvote 0

Daestrum

Expert
Licensed User
Longtime 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)
 
Upvote 0

Osp

Member
Licensed User
Longtime 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 :)
 
Upvote 0

a6000000

Member
Licensed User
Longtime User
[EDIT] see: https://www.b4x.com/android/forum/t...4j-and-from-b4j-to-webview-javascript.123547/


wvtest.zip does not work - is something NEW to watch out for?

( no error , no log )
post #9 www.b4x.com/android/forum/threads/making-upcalls-from-javascript-to-javafx.51794/post-374229
with
wvtest.zip :

B4X:
#Region  Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 400
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Dim wv As WebView
    Dim we As JavaObject
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
    we = asJO(wv).RunMethod("getEngine",Null)
    MainForm.RootPane.AddNode(wv,10,10,-1,-1)
    asJO(Me).RunMethod("setBridge",Array(we))
    we.RunMethod("loadContent",Array($"
    <br><button type="button" onClick="b4j.link('test')">Click Me!</button>
    <br><button type="button" onClick="b4j.link('test1')">Click Me2!</button>
    <br><button type="button" onClick="b4j.link2('test2',10)">Click Me3!</button>
    "$))

End Sub
Sub asJO(o As JavaObject) As JavaObject
    Return o
End Sub
Sub test
    Log("test")
End Sub
Sub test1
    Log("test1")
End Sub
Sub test2(s As Int)
    Log("test2 " & s)
End Sub
#if java
import netscape.javascript.JSObject;
import javafx.scene.web.WebEngine;
import java.lang.RuntimeException;
import java.lang.IllegalAccessException;
import java.lang.reflect.InvocationTargetException;

import java.lang.Enum;
import java.lang.reflect.Method;
import javafx.application.Platform;


public static class Bridge{
    public void link(String sub) {
        try{
            Class<?> c = Class.forName("b4j.example.main");
            final Method m = c.getDeclaredMethod("_" + sub.toLowerCase());
            final Object dummy = null;
            Platform.runLater(new Runnable(){
                public void run(){
                    try{
                        m.invoke(dummy);
                    }catch (IllegalAccessException e){
                        System.out.println(e);
                    }catch (InvocationTargetException ite){
                        System.out.println(ite);
                    }
                }
            });
        } catch (Exception e){
            System.out.println(e);
        }
    }
    public void link2(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);
        }
    }

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



[solved]
Oh! with layout file with WebView and load file:///.. html it works now :)
B4X:
' B4J  !
#Region  Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region
' wvtest30  js2B4J2js
' https://www.b4x.com/android/forum/threads/making-upcalls-from-javascript-to-javafx.51794/post-771868
' https://www.b4x.com/android/forum/threads/b4j-jscriptengine-jsbridge-js-webviev-to-b4j-to-js-working-example.123516/post-771874
' http://www.mediafire.com/file/q3x4mabuxhp8981/8_B4J2jsBRIDGE.zip/file
' see https://www.b4x.com/android/forum/threads/jscriptengine.35781/
' see https://www.b4x.com/android/forum/threads/making-upcalls-from-javascript-to-javafx.51794/#post-374183

' lib
' JavaObject 2.06, JCore 7.51, jFX 8.30


Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
   
    Dim wv As WebView
    Dim we, wvjo As JavaObject
   
    'wv js to B4J
    Private TextArea1 As TextArea
    Private Button1 As Button
    Public htmname As String = "wv30.html"
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("1") 'Load the layout file.
    MainForm.Show
   
    we = asJO(wv).RunMethod("getEngine",Null)
    asJO(Me).RunMethod("setBridge",Array(we))
   
    wv.LoadUrl("file:///../B4X/" & htmname)  'c:/B4X/wv30.html
    Wait For wv_PageFinished (Url As String)
End Sub

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

Sub teststr(s As String)
    Log("teststr " & s)
    TextArea1.Text = s
End Sub

Sub testint(ii As Int)
    Log("testint " & ii)
    TextArea1.Text = ii
End Sub

'B4J2wv
Sub ExecuteJavaScript (wv2 As WebView, script As String) As Object
    Dim jo As JavaObject = wv2
    Return jo.RunMethodJO("getEngine", Null).RunMethod("executeScript", Array(script))
End Sub

Sub Button1_Click
    Dim jstr As String = TextArea1.Text
    jstr = "B4J2wv("""  &  jstr  & """)"
    Log(jstr)
    ExecuteJavaScript(wv, jstr)
End Sub
'B4J2wv end


#if java
import netscape.javascript.JSObject;
import javafx.scene.web.WebEngine;
import java.lang.RuntimeException;
import java.lang.IllegalAccessException;
import java.lang.reflect.InvocationTargetException;

import java.lang.Enum;
import java.lang.reflect.Method;
import javafx.application.Platform;


public static class Bridge{
    public void link(String sub) {
        try{
            Class<?> c = Class.forName("b4j.wvtest21.main");
            final Method m = c.getDeclaredMethod("_" + sub.toLowerCase());
            final Object dummy = null;
            Platform.runLater(new Runnable(){
                public void run(){
                    try{
                        m.invoke(dummy);
                    }catch (IllegalAccessException e){
                        System.out.println(e);
                    }catch (InvocationTargetException ite){
                        System.out.println(ite);
                    }
                }
            });
        } catch (Exception e){
            System.out.println(e);
        }
    }
    public void link2(String sub,Object arg) {   // for call with  1 param
        boolean isInt = false;
        int ti = 0;
        try{
            Class<?> c = Class.forName("b4j.wvtest21.main");
            Class<?> ac = arg.getClass();
            /*
                    Sub Subname(s As String)
                        Log(s)
                    End Sub
              forum change line: Then it will call the sub with an Object parameter is Sub subname(ob As Object)
                Class<?> ac = Object.class;
                    Sub Subname(ob As Object)
                        Log(ob)
                    End Sub
            */
            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);
        }
    }

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


#Region designer 1.bjl
#If txttxt

    WebView: wv

     ------
    
    Label1:"in B4J"
    TextArea1:" ", Button1: "send to WebView"

#End If
#End Region


#Region show content of  'c:/B4X/wv30.html
#If txtxt

<!doctype html><html lang="en-US">
<head>
<script type="text/javascript">
    // B4JSub2jsWV
    function B4J2wv(message){
        document.getElementById("inputField").value = message;
    }
    // jsWV2B4JSub
    function wv2B4J(){
       var msg = document.getElementById("inputField").value;
       b4j.link2('teststr',msg)
    }
    function wvInt2B4J(){
       var msg = document.getElementById("inputInt").value;
       msg = parseInt(0 + msg)
       b4j.link2('testint',  msg )
    }   
</script>
<style>
    .tit {
        color:#b69221;
        font-size:300%;
        margin-top:-11px;
    }
    body {
       background-color:#fbd7bd;
    }
</style></head>
<body>
<center><h1 class="tit">in WebView </h1></center>
    <input id="inputField"  type="text" value="">
    <input onClick="wv2B4J()"  type="button" value="send str to B4J"/>
    <br>
    <input id="inputInt"  type="text" value="">
    <input onClick="wvInt2B4J()"  type="button" value="send int to B4J"/>
    <!--input id="inputFieldtest"  type="text" value="test">
    <br><button Type="button" onClick="myfunction()">Click myfunc</button-->
</body></html>


#End If
#End Region
 
Last edited:
Upvote 0

swChef

Active Member
Licensed User
Longtime User
The examples were all set up around the Main module. I wanted to use this in a class. I also wanted to have multiple instances, using B4j to retain a reference and later dispose of if no longer needed. I looked at the B4J compiler output (src debug file) and found the signature needed, and adjusted the wvtest example. Here's the changes I made.

B4X:
'B4J, in class 'myclass' in project b4a.example. If/when you use a different class name, or project, you have to make corresponding changes in the inline java (below)

Sub Class_Globals
    Private oBridge As Object
End Sub

' setClassBridge (aka addClassBridge) now returns a reference to a bridge instance
Public Sub AddBridge ' or perhaps in Initialize
    Dim joMe As JavaObject = Me
    oBridge = joMe.RunMethod("setClassBridge",Array(joWVeng, Me)) ' provided for cleanup
End Sub

Public Sub RemoveBridge
    Dim joMe As JavaObject = Me
    joMe.RunMethod("removeClassBridge",Array(oBridge))
End Sub

B4X:
' changed inline java, to accommodate my class 'myclass' in b4j.example.myclass
'note: this must be in the 'myclass' module source
#if java
import netscape.javascript.JSObject;
import javafx.scene.web.WebEngine;
import java.lang.RuntimeException;
import java.lang.IllegalAccessException;
import java.lang.reflect.InvocationTargetException;

import java.lang.Enum;
import java.lang.reflect.Method;
import javafx.application.Platform;

import java.util.ArrayList;
import java.util.List;
 
 
private static List<Bridge> stBridge = new ArrayList<Bridge>();

public static void removeClassBridge(Object b) {
    stBridge.remove(b);
}

public static Object setClassBridge(WebEngine we, myclass refMyC){
    JSObject jsobj = (JSObject) we.executeScript("window");
    Bridge b = new Bridge(refMyC);
    stBridge.add(b);
    jsobj.setMember("b4j", b);   
    return b;
}

public static class Bridge{
    private b4j.example.myclass myCref;
    public Bridge(myclass context) {
    myCref = context;
    System.out.println(context);
    System.out.println(this);
    }
    
    public void link(String sub) {
        try{
            Class<?> c = Class.forName("b4j.example.myclass");
            
            Class[] cArg = new Class[1];
            cArg[0] = b4j.example.myclass.class;
            
            final Method m = c.getDeclaredMethod("_" + sub.toLowerCase(), cArg);
            final Object dummy = myCref; //null;
            final Object xarg = myCref;
            
            Platform.runLater(new Runnable(){
                public void run(){
                    try{
                        m.invoke(dummy,xarg);
                    }catch (IllegalAccessException e){
                        System.out.println(e);
                    }catch (InvocationTargetException ite){
                        System.out.println(ite);
                    }
                }
            });
        } catch (Exception e){
            System.out.println(e);
        }
    }
    public void link2(String sub,Object arg) { // for call with  1 param
        boolean isInt = false;
        int ti = 0;
        try{
            Class<?> c = Class.forName("b4j.example.myclass");
            
            Class[] cArg = new Class[2];
            cArg[0] = b4j.example.myclass.class;
            
            Class<?> ac = arg.getClass();
            if (arg instanceof java.lang.Integer){
                ac = int.class;
                cArg[1] = ac;
            }
            final Method m = c.getDeclaredMethod("_" + sub.toLowerCase(), cArg);
            final Object dummy = myCref; //null;
            final Object xarg1 = myCref;
            final Object xarg = arg;
            Platform.runLater(new Runnable(){
                public void run(){
                    try{
                        m.invoke(dummy,xarg1,xarg);
                    }catch (IllegalAccessException e){
                        System.out.println(e);
                    }catch (InvocationTargetException ite){
                    }
                }
            });
        } catch (Exception e){
            System.out.println(e);
        }
    }

}
#end if
 
Upvote 0
Top