Trying to export non-static methods to javascript via JSNI

286 views
Skip to first unread message

rjcarr

unread,
Feb 15, 2014, 1:51:13 AM2/15/14
to google-we...@googlegroups.com
The subject sums it up pretty well, so let's get right to code:

public class Main implements EntryPoint {
 
public void onModuleLoad() {
   
export();
 
}

 
public void log(String msg) {
 
...
 
}
 
 
public static native void export() /*-{
   $wnd.log = $entry(this.@package.Main::log(Ljava/lang/String));

 }-*/;


This doesn't work for me and I get giant stack traces in my javascript logs.  I got this idea from the JSNI docs so not sure where I'm going wrong.  However, if I change things to this:

public class Main implements EntryPoint {
 
public void onModuleLoad() {
   
export();
 
}

 
public static void log(String msg) {
 
...
 
}
 
 
public static native void export() /*-{
   $wnd.log = $entry(@package.Main::log(Ljava/lang/String));

 }-*/;


Then it works fine.  What am I doing wrong?  Thanks!

rjcarr

unread,
Feb 15, 2014, 1:55:07 AM2/15/14
to google-we...@googlegroups.com
I should point out the differences.  In the first non-working example I use 'this' in my export.  In the second example I remove the 'this' and declare my log() method as static.

Also, I there's a typo and my string type signatures, they should be followed by a semi-colon, so Ljava/lang/String;

Jens

unread,
Feb 15, 2014, 6:33:08 AM2/15/14
to google-we...@googlegroups.com
I should point out the differences.  In the first non-working example I use 'this' in my export.  In the second example I remove the 'this' and declare my log() method as static.

You already answered your own question more or less. Your export method is static while your log method is not. That means you can not access your log method using "this" inside a static JSNI method.

So you either make export() non static and keep "this", or you keep export() static and provide an instance as parameter if you want to keep log() an instance method or you make all methods static and remove "this".


All instance methods:


public
 class Main implements EntryPoint {
 
public void
 onModuleLoad() {
   this.
export();

 
}

 
public void log(String msg) {
  
...
 
}

 
 
public native void export() /*-{

   $wnd.log = $entry(this.@package.Main::log(Ljava/lang/String));

 }-*/;



Static export() but instance log() method:


public
 class Main implements EntryPoint {
 
public void
 onModuleLoad() {
   Main.
export(this);

 
}

 
public void log(String msg) {
  
...
 
}

 
 
public static native void export(Main main) /*-{
   $wnd.log = $entry(main.@package.Main::log(Ljava/lang/String));

 }-*/;



All static methods:



public
 class Main implements EntryPoint {
 
public void
 onModuleLoad() {
   Main.
export();

 
}

 
public static void log(String msg) {
  
...
 
}
 
 
public static native void export() /*-{

Thomas Broyer

unread,
Feb 15, 2014, 6:59:42 AM2/15/14
to google-we...@googlegroups.com
You need to "bind" the method to the instance, e.g.

$wnd.log = $entry(function(msg) { instance.@pack.age.Main::log(Ljava/lang/String;)(msg); });

Note that 'instance' cannot be 'this' here, because 'this' is a keyword, not a variable. You could for instance pass your 'this' as argument to the export() method so you have a variable pointing to your instance, or you could just store your instance in a variable outside the closure:

var that = this;
$wnd.log = $entry(function(msg) { that.@pack.age.Main::log(Ljava/lang/String;)(msg); });
Message has been deleted

Robert J. Carr

unread,
Feb 15, 2014, 12:00:47 PM2/15/14
to google-we...@googlegroups.com
Thanks for the quick responses.  That makes sense that since the jsni method is declared static that you can't use this, but I'm not following Thomas's explanation.  Maybe I could just get an explanation from the docs example here:

http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html#methods-fields

Under the example: Accessing Java fields from JavaScript

Where it has:

public class JSNIExample {

  String myInstanceField;
  static int myStaticField;

  void instanceFoo(String s) {
    // use s
  }

  static void staticFoo(String s) {
    // use s
  }

  public native void bar(JSNIExample x, String s) /*-{
    // Call instance method instanceFoo() on this
    this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);

    // Call instance method instanceFoo() on x
    x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);

    // Call static method staticFoo()
    @com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);
What's the difference here between using the 'this' and the passed in 'x'?  What does 'this' represent in this example?

As for Thomas's explanation, are you saying that by calling $entry it creates a new inner function (or closure) where 'this' is no longer what it was?  So if I didn't use $entry then 'this' would be what I expect it is?

Thanks again for the help!

Robert


On Sat, Feb 15, 2014 at 4:26 AM, Jens <jens.ne...@gmail.com> wrote:
>>
>> $wnd.log = $entry(function(msg) { instance.@pack.age.Main::log(Ljava/lang/String;)(msg); });
>
>
>  Oh right. Somehow I totally ignored the method parameter that needs to be passed around.
>
>
> -- J.
>
> --
> You received this message because you are subscribed to a topic in the Google Groups "Google Web Toolkit" group.
> To unsubscribe from this topic, visit https://groups.google.com/d/topic/google-web-toolkit/rJAnzDPb_2Y/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to google-web-tool...@googlegroups.com.
> To post to this group, send email to google-we...@googlegroups.com.
> Visit this group at http://groups.google.com/group/google-web-toolkit.
> For more options, visit https://groups.google.com/groups/opt_out.

Thomas Broyer

unread,
Feb 15, 2014, 12:26:04 PM2/15/14
to google-we...@googlegroups.com


On Saturday, February 15, 2014 6:00:47 PM UTC+1, rjcarr wrote:
Thanks for the quick responses.  That makes sense that since the jsni method is declared static that you can't use this, but I'm not following Thomas's explanation.  Maybe I could just get an explanation from the docs example here:

http://www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html#methods-fields

Under the example: Accessing Java fields from JavaScript

Where it has:

public class JSNIExample {

  String myInstanceField;
  static int myStaticField;

  void instanceFoo(String s) {
    // use s
  }

  static void staticFoo(String s) {
    // use s
  }

  public native void bar(JSNIExample x, String s) /*-{
    // Call instance method instanceFoo() on this
    this.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);

    // Call instance method instanceFoo() on x
    x.@com.google.gwt.examples.JSNIExample::instanceFoo(Ljava/lang/String;)(s);

    // Call static method staticFoo()
    @com.google.gwt.examples.JSNIExample::staticFoo(Ljava/lang/String;)(s);
What's the difference here between using the 'this' and the passed in 'x'?  What does 'this' represent in this example?

The 'this' is the same here as it would be in Java, difference between 'this' and 'x' is the same too.
 

As for Thomas's explanation, are you saying that by calling $entry it creates a new inner function (or closure) where 'this' is no longer what it was?  So if I didn't use $entry then 'this' would be what I expect it is?

It has nothing to do with $entry() but the fact that you export a method of an object, and the way things (and specifically 'this') work in JavaScript.

Let's use pure JS:

function JSExample() { };
JSExample.prototype.instanceFoo = function(s) {
  // use s
};
JSExample.staticFoo = function(s) {
  // use s
};
JSExample.prototype.bar = function(x, s) {
  this.instanceFoo(s);
  x.instanceFoo(s);
  JSExample.staticFoo(s);
};

We can create a JSExample instance and call bar:

var y = new JSExample();
y.bar(new JSExample(), "msg");

Now export the method from 'y' without binding it to the JSExample instance and try to call it:

var foo = y.bar; // could be window.foo instead of var foo; y.bar is the same as JSExample.prototype.bar
foo(new JSExample(), "msg"); // fails because 'window' (the current 'this') does not have an 'instanceFoo' property
foo.call(y, new JSExample(), "msg"); // works because we explicitly set the 'this' to 'y'

Now export the method, binding it to 'y' (using a closure) and call it:

var foo = function(x, s) { y.bar(x, s); };
foo(new JSExample(), "msg"); // works, because we call 'bar' on 'y'

Introduce $entry:

var foo = $entry(function(x, s) { y.bar(x, s); });
foo(new JSExample(), "msg"); // works the same

With ECMAScript 5, you could now use y.bar.bind(y) instead of using a closure, and with "use strict" the "default 'this'" would be 'null', not 'window'.

When I say GWT doesn't free you from knowing JS, I really mean it!

Robert J. Carr

unread,
Feb 15, 2014, 4:54:13 PM2/15/14
to google-we...@googlegroups.com
Thanks for the explanation and examples.  I actually know javascript quite well, including the ramifications of using this inside of closures, but as I said, I didn't know that $entry was setting up a closure.

You've given me enough information that I believe I can figure out my problems now.  Thanks for helping me out!


--

Thomas Broyer

unread,
Feb 15, 2014, 6:05:39 PM2/15/14
to google-we...@googlegroups.com


On Saturday, February 15, 2014 10:54:13 PM UTC+1, rjcarr wrote:
Thanks for the explanation and examples.  I actually know javascript quite well, including the ramifications of using this inside of closures, but as I said, I didn't know that $entry was setting up a closure.

It's not. It's taking a function as argument and returns a function. If you pass your 'y.bar' (to keep my pure-JS example) as argument, it'll ultimately be called just like my "var foo", and depending on the 'this' at the time of the call, it'll fail.
So you *have* to create a closure to bind 'bar' to 'y' (and pass that function to $entry), or you could use $entry(y.bar.bind(y)) which would be equivalent (but IE9+).

$entry basically is:

function $entry(f) {
  return function() {
    try {
      run_entry_commands(); // see Scheduler#scheduleEntry
      try {
        f.apply(this, arguments);
      } finally {
        run_finally_commands(); // see Scheduler#scheduleFinally
      }
    } catch (e) {
      reportUncaughtException(e); // see GWT#reportUncaughtException
    }
  };
}

which, for the purpose of the example/demo, could be simplified as:

function $entry(f) {
  return function() { f.call(this, arguments); }
}

or even

function $entry(f) { return f; }

Reply all
Reply to author
Forward
0 new messages