Search code examples
scalanashorn

How can I combine this context with invokeFunction in Nashorn?


I am trying to call a function in Javascript from Java/Nashorn (in Scala, but that's not material to the question).

// JS
var foo = function(calculator){  // calculator is a Scala object
  return this.num * calculator.calcMult();
}

The context on the Scala side is like this:

case class Thing(
  num: Int,
  stuff: String
)

case class Worker() {  // Scala object to bind to calculator
  def calMult() = { 3 }  // presumably some complex computation here
}

I start by getting foo into the JS environment:

jsengine.eval("""var foo = function(calculator){return this.num * calculator.calcMult();}"""

To use this I need two things to be available: 1) 'this' context to be populated with my Thing object, and 2) the ability to pass a Java/Scala object to my JS function (to call calcMulti later). (If needed I can easily JSON-serialize Thing.)

How can I do both and successfully call foo() from Scala?


Solution

  • This may not be the only or cleanest solution, but it does work.

    Turns out javascript has the ability to bind a given 'this' context to a function, which creates a "bound function" that has your 'this' visible within it. Then you use invoke() as you normally would on the bound function.

    val inv = javascript.asInstanceOf[Invocable]
    val myThis: String = // JSON serialized Map of stuff you want accessible in the js function
    
    val bindFn = "bind_" + fnName
    javascript.eval(bindFn + s" = $fnName.bind(" + myThis + ")")
    
    inv.invokeFunction(bindFn, args: _*)
    

    If you passed myThis into the binding to include {"x":"foo"} then when invoked, any access within your function to this.x will resolve to "foo" as you'd expect.