views:

413

answers:

5

Hi, i have an ActiveX Object (Master) and would like to invoke functions dynamically on it. To do this i use the apply() Function. But sadly the InternetExplorer tells me something along the lines of: "This Object doesn't support this Method". Can someone give me a hint what i could do?

(To test this you also could use a small flash object as Master and call "doSomething" instead of my specific "Initialize".)

function invoke(object, fnName, args)
{
  return object[fnName].apply(object, args);
}

function test_it()
{
  try{
    Master = window.document["Master"];
  }
  catch(e){alert(e);}
  var param = [1,"VC2"]; 
  var ret = invoke(Master, "Initialize", param);
  alert("got: "+ret);
}

For comparsion, this is the apply() Function in action:

function Obj()
{
  this.msg = function(a, b, c)
  {
      alert("msg: \n a: "+a+"\n b: "+b+"\n c: "+c);
      return "hi";
  }
    return this;
}


function invoke(object, fnName, args)
{
  return object[fnName].apply(object, args);
}

function test_it()
{
  var obj = new Obj();
  var ret = invoke(obj, "msg", [1, 2, 3]);
  alert("got: "+ret);
}
A: 

If you really have to have an invoke function to which you pass a function name as a string and arguments as an array, Ates Goral's eval suggestion will work.

Tim Down
fn.apply(obj, [ 1, 2 ]) and obj.fn([ 1, 2 ]) are not the same thing. The apply translates to obj.fn(1, 2).
Ates Goral
Ah, good point: I forgot the difference between apply and call. Oops. Amending my answer.
Tim Down
+1  A: 

Apparently IE's JS engine doesn't see ActiveX functions as JavaScript Function objects on which you can call apply(). How about just doing an eval() -- although ugly, it seems to be your only option.

function invoke(objectName, fnName, args) {
    return eval(objectName + "." + fnName + "(" + args + ")");
}
Ates Goral
+2  A: 

The problem with some of the host objects (i.e. any non-native objects) in IE (and not only IE) is that they don't inherit from Function.prototype (and often neither from top level Object.prototype). Some host objects that might look like functions actually have nothing to do with functions except that they can be called. The fact that these objects don't inherit from Function.prototype means that they fail to be identified as functions with instanceof operator; that their constructor is not referencing Function; and that they lack all of the Function.prototype.* methods, such as call or apply. Even their internal [[Class]] property might not be that of "Function", as it is with any native object (note that [[Class]] can be inferred from the result of Object.prototype.toString value).

This is actually expected, since host objects are not required to implement many things that native objects do (as per ECMA-262, 3rd ed.). It is perfectly allowed for a host object to, say, throw error on method invocation (e.g. hostObject.hostMethod()); or when passing it as an operand to standard operators like delete (e.g. delete hostObject.hostMethod). As you can see, it is also OK for callable host objects to NOT inherit from native Function.prototype.

Such unpredictable (yet perfectly compliant) behavior is actually one of the main reasons why host objects augmentation is recommended against.

But back to your call problem : )

The thing about these "tricky" IE host objects is that they often implement internal [[Call]] method, and it is possible to invoke call and apply on them, although not directly.

Here's a pattern to emulate apply invocation on an object that doesn't have it:

function f(){ return arguments };
Function.prototype.apply.call(f, null, [1,2,3]); // [1,2,3]

null can be replaced with whatever context object should be called in, of course.

And an example of apply invocation on host object that has no call:

// should work in IE6, even though `alert` has no `call` there
Function.prototype.call.call(alert, window, 'test');
kangax
+1  A: 

Thanks kangax for your time and your extensive explanation! Sadly i could not get it working this way(it works for the alertbox though) But it led me to the idea to use a proxy-class. It's not the most elegant way because i have to provide every function from my object i want to use but it works AND it doesn't involve eval()!

function proxy(obj)
{
    this.obj = obj;

    this.Initialize = function(a, b)
    {
     return obj.Initialize(a, b);
    } 
}

function test_it()
{
    var myMaster = new proxy(window.document["Master"]); 
    var ret = myMaster["Initialize"].apply(myMaster, [1, "VC2"]);
    alert(ret);
}

Again, thanks for your time!

hobotron
Alright. Glad it helped : )
kangax
A: 

Just thought I'd mention that if you use the eval method as Ates Goral said you need to be careful of string arguments in your array as they will be considered variable names, eg

function invoke(objectName, fnName, args) {
    return eval(objectName + "." + fnName + "(" + args + ")");
}
invoke("Master", "Initialize", [1, "VC1"]);

the eval will be passed the line

Master.Initialize(1,VC1)

which will throw an error if VC1 is not a defined variable. It might be best to "unroll" the array name instead of passing literals:

function UnrollArray(arrayname, length) {
    var s = "";
    for(var i = 0; i < length; i++) {
        s += arrayname + "[" + i + "],";
    }
    return s.substring(0, s.length - 1); //remove the trailing comma
}

so invoke becomes

function invoke(objectName, fnName, args) {
    var unrolledarray = UnrollArray("args", args.length);
    return eval(objectName + "." + fnName + "(" + unrolledarray + ");");
}
invoke("Master", "Initialize", [1, "VC1"]);

the eval will then be passed

Master.Initialize(args[0],args[1]);
thegravytalker