views:

447

answers:

3

Hi, I've encountered an curious problem while trying to use some objects through JSNI in GWT. Let's say we have javscript file with the function defined:

test.js:

function test(arg){
  var type = typeof(arg);
  if (arg instanceof Array)
    alert('Array');
  if (arg instanceof Object)
    alert('Object');
  if (arg instanceof String)
    alert('String');
}

And the we want to call this function user JSNI:

public static native void testx()/ *-{
  $wnd.test( new Array(1, 2, 3) );
  $wnd.test( [ 1, 2, 3 ] );
  $wnd.test( {val:1} );
  $wnd.test( new String("Some text") );
}-*/;

The questions are:

  • why instanceof instructions will always return false?
  • why typeof will always return "object" ?
  • how to pass these objects so that they were recognized properly?
A: 

your test function always returns false because you do not supply a return statement... and String is funny in JavaScript... if you use new String("asdf"); then using instanceof will work, if you just create a string with "asdf" then you will need to use typeof.

function test(arg){
  if (arg instanceof Array){
    return 'Array';
  } else if(arg instanceof String || typeof(arg) == 'String'){
    return 'String';
  } else if (arg instanceof Object){
    return 'Object';
  } else {
    return typeof(arg);
  }
}

(note there are other types... Date, Number, Custom Objects etc.)

scunliffe
+2  A: 

instanceof shouldn't be returning false all the time in your example unless you're testing objects from a different window, because an array from one window is not an instance of the Array constructor of a different window.

Using instanceof is great when you need to test for a specific thing and you're operating within one window (you do have to be aware of the string primitive vs. String object thing that scunliffe pointed out). Note that you need to be careful of your order, since an array is an instanceof Object (as well as Array); this applies to Strings and all other objects as well.

There's an alternative that doesn't have the window issue and which can readily be used for switch statements and the like if you're doing dispatch:

function classify(arg) {
    return Object.prototype.toString.call(arg);
}

That looks odd, but what it does is use the toString function on the Object prototype, which has a defined behavior (rather than using any override that the actual object you're testing may have, which may have different behavior). So given this function:

function show(arg) {
    alert(classify(arg));
}

you'll get these results:

show({});               // [object Object]
show("a");              // [object String]
show(new String("a"));  // [object String]
show([]);               // [object Array]
show(/n/);              // [object RegExp]
show(function() { });   // [object Function]

and you'll get those results regardless of what window the object you're testing is coming from and regardless of whether you use a string primitive or a String instance.

T.J. Crowder
Impressive in its simplicity!
scunliffe
+1  A: 

Since everything else seem to have been answered let me get this one:

how to pass these objects so that they were recognized properly?

GWT does this automagically for primitive types like Strings, Integers, etc. So you can write just:

public static native String test1()/ *-{
   return "adfasdf";
}-*/;

public static native int test2()/ *-{
   return 23;
}-*/;

See the docs for some extra notes.

For arrays, there are a bunch of wrapper classes: JsArray, JsArrayBoolean, JsArrayInteger, JsArrayNumber, JsArrayString.

public static native JsArrayString test3()/ *-{
   return ['foo', 'bar', 'baz'];
}-*/;
Igor Klimer