Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

html and <button onclick='someFn'> where the someFn is generated from TeaVM...

128 views
Skip to first unread message

James Moliere

unread,
May 4, 2024, 1:09:07 PM5/4/24
to TeaVM
Given an html that is dynamically generated with a button...
<button onclick='someFn()' ...>

How can I generate the 'someFn()' function in Javascript using TeaVM?
I've tried a number of things to no avail.

@JSExport
@JSProperty
public static void renderDialogJava()
{
    System.out.println("TeaVM is cool");
}

@JSBody(params = { }, script =
"return javaMethods.get('teavm.view.RenterListView.renderDialogJava()V;')"
+ ".invoke();")
public static native void someFn();


Alexey Andreev

unread,
May 4, 2024, 1:29:15 PM5/4/24
to te...@googlegroups.com

Hello. Can you please clarify? What actually are you trying to do?


--
You received this message because you are subscribed to the Google Groups "TeaVM" group.
To unsubscribe from this group and stop receiving emails from it, send an email to teavm+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/teavm/9b0a21cd-9fd6-4fcf-868d-a72c786d705an%40googlegroups.com.

James Moliere

unread,
May 4, 2024, 1:59:55 PM5/4/24
to TeaVM
Upon a click of a button, I'd like the 'someFn()' function get executed.  I'd like to generate the someFn() function using Java to receive the click.

The output of clicking the button is...
index.html:1 Uncaught ReferenceError: someFn is not defined
    at HTMLButtonElement.onclick (index.html:1:1)
Message has been deleted

Alexey Andreev

unread,
May 6, 2024, 4:23:38 AM5/6/24
to TeaVM
First of all, you can alternatively add event listener to HTML element from within script, like this:

var button = document.getElementById("my-button-id");
button.onClick(() -> System.out.println("TeaVM is cool"));

second, if you still want to use @JSExport annotation, please read this.

In your particular case I don't have access to your code and therefore can't figure out why it does not work for you.

суббота, 4 мая 2024 г. в 19:59:55 UTC+2, james....@gmail.com:

James Moliere

unread,
May 6, 2024, 11:05:02 AM5/6/24
to TeaVM

I understand that solution you mentioned, I am trying to get a feel as to where 'the walls' are with this technology.  In the code I have below, I create the whole table (with onclick='someFn(index);' and expect the Javascript function 'to be there' when called.

I will layout the classes...

Main.java
//////////////////////////////
public class Main {

private static final HTMLDocument document = Window.current().getDocument();

public static void main(String[] args) {
XMLHttpRequest xhr = XMLHttpRequest.create();
xhr.onComplete(() -> Main.receiveResponse(xhr));
xhr.open("GET", "teavmList.json" //""/teavmList.json"
);
xhr.send();
}

private static void receiveResponse(XMLHttpRequest xhr) {

if (xhr.getReadyState() != XMLHttpRequest.DONE) {
return;
}
String text = xhr.getResponseText();
JSObject json = JSON.parse(text);
JSArray<JSMapLike<JSObject>> itemJSArray = json.cast();

ItemListView itemListView = new ItemListView();
String html = itemListView.renderRenterJSArray(itemJSArray);
HTMLElement div = HTMLDocument.current().createElement("div");
div.setInnerHTML(html);
document.getBody().appendChild(div);
}

public static void handleClick() {
System.out.println("got called");
}

@JSBody(params = { "index" }, script =
"return javaMethods.get('teavm.ItemListView.renderDialogJava(I)V;')"
+ ".invoke(index);")
public static native void someFn( int index);

}
////////////////////////////////////////


ItemListView.java
///////////////////////////////////////
public class ItemListView {

JSArray<JSMapLike<JSObject>> jsArray ;
final static String headerTemplate = """
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Learn More</th>
</tr>
%s
<table>
""";

public String renderRenterJSArray(JSArray<JSMapLike<JSObject>> jsArray)
{
this.jsArray = jsArray;
StringBuilder sb = new StringBuilder();
for (int i=0;i<jsArray.getLength();i++)
{
JSMapLike<JSObject> item = jsArray.get(i);
String row = renderRow(item, i);
sb.append(row);
}
return format(headerTemplate, sb);
}

private static String renderRow(JSMapLike<JSObject> item, int indexCounter) {
String row = STR."""
<tr>
<td style="text-align: center">\{item.get("id")}</td>
<td>\{item.get("name")}</td>
<td>\{item.get("description")}</td>
<td><button onclick='someFun( \{indexCounter} )'>Learn More</button></td>
</tr>
""";
return row;
}

@JSExport
public static void renderDialogJava( int i)

{
System.out.println("TeaVM is cool");
}
}
///////////////////////////////////


build.gradle
/////////////////////////////
plugins {
id 'java'
id 'war'
id "org.teavm" version "0.10.0"
}

sourceCompatibility = 21
targetCompatibility = 21
group = 'us.moliere.rental.teavm'
version = '1.0'

repositories {
mavenCentral()
}

dependencies {

implementation teavm.libs.jso

testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
}

teavm {
js {
mainClass.set("teavm.Main")
sourceMap.set(true)
obfuscated.set(false)
debugInformation.set(true)
sourceMap.set(true)
targetFileName.set("main.js")
addedToWebApp = true
}
}

test {
useJUnitPlatform()
}

war {
archiveFileName = "ROOT##${project.version}.war"
}

war {
doLast {
copy {
print("exploded war called; $buildDir}")
into "${buildDir}/exploded"
with war
}
}
}

compileJava {
options.compilerArgs += ['--enable-preview']
}
//////////////////////////



teavmList.json
///////////////////////
[
{
"id": 1,
"name": "TeaVM reasons to use",
"description": "Compile, test, and deploy with Java's Gradle or Maven",
"learn_more": "learn more 1"
},
{
"id": 2,
"name": "Advantage of TeaVM",
"description": "Intuitive and easy to use",
"learn_more": "learn more 2"

}
]
////////////////////////


index.html
///////////////
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" charset="utf-8" src="js/main.js"></script>
</head>
<body onload="main()"></body>
</html>

/////////////////////////

Alexey Andreev

unread,
May 6, 2024, 12:46:56 PM5/6/24
to te...@googlegroups.com
Sorry, I don't understand the logic behind the code. Why does `@JSExport` stands where it stands? Did you read documentation?

James Moliere

unread,
May 6, 2024, 8:56:02 PM5/6/24
to TeaVM
Hello,
I read the documentation and learned that when I put the declaration in ItemListView.java ...
@JSExport
public static void someFun( int index)
{
System.out.println(STR."TeaVM is cool with index: \{index}");
}
... the 'someFun(int index)' does not get exported properly with an error...

Uncaught
 ReferenceError: someFun is not defined
onclick index.html:1

When I put the same declaration above into Main.java, the function works as expected.

I tried adding the Annotation below to the Main class...
@JSExportClasses({ ItemListView.class })
public class Main {...}

...and was still unable to get ItemListView.someFun( int index ) to work.

Thank you for your help. 

If there is a way to get 'ItemListView.someFun(int index)' to work instead of putting the declaration in Main, I'd appreciate it.

James Mullowney

unread,
Mar 14, 2025, 2:02:27 AMMar 14
to TeaVM
Hi, 
Sorry to bump this thread but I'm having similar problems. Its not clear to me how I can structure my code so that I can call a method in my java widget from a javascript function. My widget does not meet the criteria of JSObject so I'm trying an inner class.

/**

* Bridges calls from javascript to the widget

*/

class Bridge implements JSObject {

@JSProperty

public void loadRosette(String id) {

try {

RosetteWidget.this.retrieveRosette(Long.parseLong(id));

} catch (NumberFormatException e) {

if (Client.isLoggable(Level.WARNING)) {

LOG.warning("Invalid id. Can't load rosette");

}

}

}

@JSBody(script = "window.widgetBridge = this;")

public static native void exposeToJavaScript();

}


This compiles. I try window.widgetBridge.loadRosette("2934") in javascript some time after the page and widget have loaded. It errors because window.widgetBridge is null. I instantiate a Bridge at the end of the widget's init routine (called from main) and call exposeToJavaScript() there. Am I  supposed to call that myself?

I also tried to add a JSBody method to the Client (main) class, like James but its not working for me.

@JSBody(params= "id", script = "return javaMethods.get('org.zfcj.rosettes.client.RosetteWidget.loadRosette(Ljava/lang/String;)V;').invoke(widget, id);")

public static native void loadRosette(String id);


where widget is a static field initialized in main(). I suspect this is incorrect usage. It errors with
 
    [ERROR] Can't call method org.zfcj.rosettes.client.RosetteWidget.loadRosette(Ljava/lang/String;)V of non-JS class


Can anyone help?


Alexey Andreev

unread,
Mar 14, 2025, 3:47:16 AMMar 14
to TeaVM
Hi,
 
Sorry to bump this thread but I'm having similar problems. Its not clear to me how I can structure my code so that I can call a method in my java widget from a javascript function. My widget does not meet the criteria of JSObject so I'm trying an inner class.

/**

* Bridges calls from javascript to the widget

*/

class Bridge implements JSObject {

@JSProperty

public void loadRosette(String id) {

try {

RosetteWidget.this.retrieveRosette(Long.parseLong(id));

} catch (NumberFormatException e) {

if (Client.isLoggable(Level.WARNING)) {

LOG.warning("Invalid id. Can't load rosette");

}

}

}

@JSBody(script = "window.widgetBridge = this;")

public static native void exposeToJavaScript();

}


This compiles. I try window.widgetBridge.loadRosette("2934") in javascript some time after the page and widget have loaded. It errors because window.widgetBridge is null. I instantiate a Bridge at the end of the widget's init routine (called from main) and call exposeToJavaScript() there. Am I  supposed to call that myself?

 
actually, I don't understand clearly what you are trying to achieve. Why is this `exposeToJavaScript`? TeaVM supports exporting classes for some time, see here: https://teavm.org/docs/runtime/js-modules.html

Also, I like "tests is documentation" methodology, so you can learn a lot from tests.

 

I also tried to add a JSBody method to the Client (main) class, like James but its not working for me.

@JSBody(params= "id", script = "return javaMethods.get('org.zfcj.rosettes.client.RosetteWidget.loadRosette(Ljava/lang/String;)V;').invoke(widget, id);")

public static native void loadRosette(String id);


where widget is a static field initialized in main(). I suspect this is incorrect usage. It errors with
 
    [ERROR] Can't call method org.zfcj.rosettes.client.RosetteWidget.loadRosette(Ljava/lang/String;)V of non-JS class


Why do you need that? What are you trying to achieve?
Reply all
Reply to author
Forward
0 new messages