scoots
scoots

Reputation: 294

GWT JSNI - JavaScriptException TypeError - method is not a function

I want to be able to call a method declared in my StartView class from the onclick event in Javascript. I am following the official GWT documentation, but am still getting a JavaScriptException TypeError - doThing is not a function error in the console. I have reviewed other similar questions here on SO and have not found a solution as of yet. I have attempted to implement a solution in JSNI and JSInterop - and what I have below is the furthest I have gotten past GWT compilation errors.

StartView.java - I call StartView.loadJS() when StartView is initialized

    public void doThing() {
        System.out.println("do thing");
    }

    public static native void loadJS() /*-{
        var that = this;
        $wnd.doThing = $entry(function() {
            [email protected]::doThing()();
        });
    }-*/;

start.js (External js file, Injected into StartView via ScriptInjector. Other methods use JSInterop)

...
// Inside of my onclick handler
 window.doThing();

When I click the element, I get the following error in the console:

Exception: com.google.gwt.core.client.JavaScriptException: (TypeError) : that_0_g$.doThing_0_g$ is not a function 

When I put window.doThing in the console, it does return the function - so it does appear to be loading the connector, but is having trouble finding the actual method to call. Curious what else I am missing. Would appreciate any advice.

Upvotes: 0

Views: 240

Answers (1)

Colin Alworth
Colin Alworth

Reputation: 18331

First, obligatory "Don't use JSNI for new code" - unless you project relies on an ancient GWT build, this is easier in JsInterop. First, define (or reuse) a @JsFunction interface that describes the contract for JS to call into Java, make a global field that you can assign this to, and then export your instance method in vanilla java:

/** Like Runnable, but no default methods, and has {@link JsFunction}. */
@JsFunction
public interface JsRunnable {
  void run(); // your example has no params and no return type, so this fits
}

public static void loadJS() {
  JsRunnable lambda = this::doThing; // bug is clear in java!

  // Assign as a new property on window
  Js.asPropertyMap(DomGlobal.window).set("doThing", lambda);
}

public void doThing() {
  System.out.println("do thing");
}

Note that the translation of loadJS makes it clear why your JSNI isn't working (more on that in a moment) - Java will complain if you try to use this, since loadJS() is static and has no this! But, you're calling loadJS() from inside the constructor, you could pass the instance as an argument, or make loadJS() non-static.


From your question

    public static native void loadJS() /*-{
        var that = this; // Where should "this" come from?
        $wnd.doThing = $entry(function() {
            [email protected]::doThing()();
        });
    }-*/;

As this is a static method, there is no this (okay, in JS there is a this, but it isn't the StartView, it is something else entirely). Since doThing is a non-static method, there is no clear way to say which StartView instance you want to have doThing() called on.

The simplest answer then is to remove static from that method, so that this is correctly captured, as you intended. Another choice could be to pass in a StartView instance to loadJS and call doThing on that instance.

The error you're getting is almost certainly that this turns out to be window, and the @package.StartView::doThing() reference represents some obfuscated method name that doesn't exist on window, which makes sense, since you intended this to be a StartView instance.

Upvotes: 2

Related Questions