How do we create a JavaScript object in a native method? - gwt

I'm working with the google maps api, I'd like to create an instance of one of their objects:
public static final native void test(double lat, double lng) /*-{
var obj = new google.maps.LatLng(lat, lng);
}-*/;
but the above does not work, prints the following error:
com.google.gwt.core.client.JavaScriptException: (ReferenceError)
#com.google.gwt.core.client.impl.Impl::apply
(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)
([JavaScript object(4), JavaScript object(3), JavaScript object(6)]):
google is not defined
so I probably have to explain to GWT what the "google.maps.LatLng" object is - how do I do that? I thought there was a specific syntax for that, but can't seem to find it anymore in the docs,
Thanks

If you define google.maps somewhere else in your host page, you must prefix it with $wnd in your GWT code:
public static final native void test(double lat, double lng) /*-{
var obj = new $wnd.google.maps.LatLng(lat, lng);
}-*/;
From GWT documentation:
When accessing the browser's window and document objects from JSNI,
you must reference them as $wnd and $doc, respectively. Your compiled
script runs in a nested frame, and $wnd and $doc are automatically
initialized to correctly refer to the host page's window and document.

Related

Javascript module function in GWT with JsInterop

Hoping this is way easier than I'm making it - I'm a Java coder, some inner Javascript aspects are a tad unfamiliar to me.
Trying to embed the great CodeJar library inside a GWT panel. There's a pretty nice/simple example for CodeJar:
<script type="module">
import {CodeJar} from './codejar.js'
import {withLineNumbers} from './linenumbers.js';
const editor = document.querySelector('.editor')
const highlight = editor => {
// highlight.js does not trim old tags,
// let's do it by this hack.
editor.textContent = editor.textContent
hljs.highlightBlock(editor)
}
const jar = CodeJar(editor, withLineNumbers(highlight), {
indentOn: /[(\[{]$/
})
jar.updateCode(localStorage.getItem('code'))
jar.onUpdate(code => {
localStorage.setItem('code', code)
})
</script>
The module function itself looks like this:
export function CodeJar(editor, highlight, opt = {}) { ... }
'editor' is a Div reference, and 'highlight' is a callback library function for handling code highlighting.
What I'm battling with is the JsInterop markup and code to make Javascript modules work with GWT. The above has a few aspects which I'm battling with
replacing the "import" such that the javascript module code is available to GWT. Obvioulsy I can just import the js in my top level index.html, but as I understand it JS modules don't become part of the global namespace, they're only usable from the JS module that imports them. Which in my case, presumably needs to be the GWT code.
how to pass the callback function in when recoding the above in GWT
how to get my own 'jar' reference to do own text set/get (replacing the use of local storage)
To load the script and have it available for GWT consumption, you have (at least) 3 possibilities:
use a static import in a <script type=module>, and then assign the CodeJar function to a window property to make it available globally (that could be another global object than window actually)
use a dynamic import() from GWT, using JsInterop and possibly elemental2-promise
use Rollup/Webpack/whatever to turn the CodeJar module into a non-module script so you can use it differently
Next, you need to create JsInterop bindings so you can call it from GWT; something like that (assuming you made CodeJar available globally as window.CodeJar, and using elemental2-dom for HTMLElement, but com.google.gwt.dom.client.Element would work just as well):
#JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "?")
interface CodeJar {
#JsMethod(namespace = JsPackage.GLOBAL, name = "CodeJar")
static native CodeJar newInstance(HTMLElement element, HighlightFn highlight);
#JsMethod(namespace = JsPackage.GLOBAL, name = "CodeJar")
static native CodeJar newInstance(HTMLElement element, HighlightFn highlight, Options opts);
void updateOptions(Options options);
void updateCode(String code);
void onUpdate(UpdateFn cb);
void destroy();
}
#JsType(isNative = true, namespace = JsPackage.GLOBAL, name = "Object")
class Options {
public String tab;
public JsRegExp indentOn;
public boolean spellcheck;
public boolean addClosing;
}
#JsFunction
#FunctionalInterface
interface HighlightFn {
void highlight(HTMLElement e);
}
#JsFunction
#FunctionalInterface
interface UpdateFn {
void onUpdate(String code);
}
With the above code, you should be able to create an editor using something like:
CodeJar jar = CodeJar.newInstance(editor, MyHighlighter::highlight);
If you use a dynamic import(), replace the static methods with instance ones in a #JsType interface representing the module received from the promise.

How to implement a javascript API using JSNI?

I am trying to implement an API (SCORM API) using GWT.
The client code expects an API object with methods like Initialize(), getLastError() and so on...
I tried to implement this api as an Java Object, but i see that the compiled names are changed and cannot be used directly by client code.
I see that gwt-exporter can do the trick (http://code.google.com/p/gwt-exporter/) but i would like to know how to do it using pure gwt and jsni.
As the API is expected as a object, named API_1484_11 attached to the window object, not an function, , i don't see how to use the $entry() idiom.
Here is my current, failing, code:
public final class SCORMApi {
protected SCORMApi() {}
public void Initialize(){
GWT.log("** INITIALIZE CALLED **");
}
public static void create(){
bind(new SCORMApi());
}
public static native void bind(SCORMApi api) /*-{
$wnd.API_1484_11 = api;
}-*/;
}
So, in this context, my question is:
How can i get javascript calls (e.g. window.API_1484_11.Initialize() ) to reach my java gwt code?
You're on the right lines with your bind method. But you haven't understood how to call Java methods from within JSNI. This is how you do it in the case of your Initialize method:
public static native void bind(SCORMApi api) /*-{
$wnd.API_1484_11 = {
initialize: function() {
$entry( api.#com.yourpackage.name.SCORMApi::Initialize()() );
}
};
}-*/;
The blogs Getting To Really Know GWT parts 1 and 2 are required reading on this subject.

Linking to GWT instance method from JSNI does not automatically bind "this"

I am going to file this as a bug report, but I wanted to check if someone here can see something wrong with what I am doing.
When you expose an instance method from a GWT class through JSNI, this works as expected in JavaScript. Since we are cross compiling Java, I would instead expect this to be bound to the instance automatically. For example:
package com.test;
class Foo {
public void instanceFunction() {
this.otherFunction() // will cause an error when called from JSNI!
}
public void otherFunction() {
// does some stuff
}
public native JavaScriptObject getInstanceFunction() /*-{
return this.#com.test.Foo::instanceFunction();
}-*/;
}
Currently the workaround is to bind the function yourself (not very portable):
public native JavaScriptObject getInstanceFunction() /*-{
return this.#com.test.Foo::instanceFunction().bind(this);
}-*/;
This can also be seen as preference, some may prefer that the functions remain unbound. I would say the current functionality is unintuitave and unnecessary. I cannot imagine a use case for having an unbound this directly in Java code. Also, some browsers do not implement bind(1), so my workaround is not robust.
If you want a portable bind, it's as easy as:
var that = this;
return $entry(function() {
return that.#com.test.Foo::instanceFunction()();
});

JSNI call a Java method with a Long param

I need to call a Java method from Javascript. So I defined the method:
private native void registerMethod() /*-{
var self = this;
$wnd.test = function(longParam) {
self.#mypackage.HomeView::test(Ljava/lang/Long;)(longParam);
};
}-*/;
The Java method:
private void test(Long longParam) {
GWT.log("Call to test with longParam = " + longParam);
}
The JS call:
public static native void paypalClose() /*-{
$wnd.alert(top.test);
top.test(10);
top.dgFlow.closeFlow();
top.close();
}-*/;
The alert shows the Javascript function definition. If I call top.test(), it works but no param is passed. But if I call top.test(10) I get a null alert window.
Your method excepts a java.lang.Long, not a long, so you have to create an instance of Long. You could use #java.lang.Long::new or #java.lang.Long::valueOf, except you cannot use longs either in JSNI: https://developers.google.com/web-toolkit/doc/latest/DevGuideCodingBasicsJSNI#important
Because JavaScript numbers are strictly equivalent to Java doubles, you should use a double or java.lang.Double as an argument. Once in the Java world, you can cast the double to a long if you like, but not from/in JSNI.

GWT Overlay Tpes check for null

I'm using GWT overlay types to parse my JSON response form the web server. It all works fine, the problem is, if the required field doesn't exist:
example:
JavaScriupt overlay type class
public class JSWorkplace extends JavaScriptObject{
protected JSWorkplace() {
}
public final native String getWidgets() /*-{
return this.Widgets;
}-*/;
now if I have something like {"Widgets":"Bla"} comes form the server everything is alright, getWidgets returns "Bla".
If this "{}" comes from the server my application throws in the gwtWidgets function. How can I check if the field "Widgets" exists before reading it.
Regards,
Stefan
You can check if it's undefined doing something like: this.Widgets == undefined.
Personally I prefer to set default values after the eval(). For example, in order to create your JSWorkplace object I would invoke a method like this:
public static native JSWorkspace createFromJSON(String json)/*-{
var object = eval('(' + json + ')');
if (object.Widgets == undefined) { object.Widgets = []; }
...
return object;
}*-/;