3

I have a Scala.js application using Udash. The application is using some Bootstrap extensions, which directly manipulate HTML DOM. I would like to traverse this DOM and add some more handlers to it (eventually I would like the handlers to implement Udash binding). My trouble is the only way I can do this is by inserting a script tag, which expects me to provide a plain Javascript code.

Is there some way I could call by Scala.js code from this Javascript? Normally I would export a global function and pass any necessary parameters to it, however I do not see any clean way how to pass this, the only way I can think of is using a global variable, which look super ugly to me. Is there anything like local exports, or some other way how to create a JavaScript code I could pass into script which would be able to access Scala.js constructs?

My current code looks like this:

// somewhere in class ExtTable .. in the `render` method
    div(
      p(id := componentId, "Html constructed here"),
      script {
        ExtTable.callback = { e =>
          println(s"JS Callback for $e on $this")
        }
        //language=JavaScript
        s"""
        // I would like to implement this in Scala.js instead
        var t = $$('#${componentId.toString}');
        t.bootstrapTable();
        t.find("tr td:first-of-type").each(function(i,e){
          ExtTable.callback(e);
        })
        """
      }
    ).render
@js.annotation.JSExportTopLevel("ExtTable")
object ExtTable {
  @js.annotation.JSExport
  var callback: js.Function1[Element, Unit] = _
}

1 Answer 1

1

TRANSLATE YOUR SNIPPED TO SCALAJS

You need to use some jquery wrapper library. Udash has its own... I'm using oldest one (here) (example below).

import org.scalajs.jquery.jQuery
import org.scalajs.dom.Element
import scala.scalajs.js

@js.annotation.JSExportTopLevel("ExtTable")
object ExtTable {
  @js.annotation.JSExport
  var callback: js.ThisFunction0[Element, Unit] = _ //look first argument is what `this` in js means
}

var t = jQuery("#"+componentId.toString);
t.asInstanceOf[js.Dynamic].bootstrapTable(); //to call function that is not known statically!
t.find("tr td:first-of-type").each( { (e: Element) =>
  ExtTable.callback(e)
})
  • To cover funtion with this in body you need to use js.ThisFunction (find more here). In above example .each(...) takes ThisFunction1[Element,_] and it means this from javascript will be our first argument (e:Element in code above). As you can see it is inferred from normal scala closure notation ({(e: Element) => ...}).
  • weird .asInstanceOf[js.Dynamic] part is necessary here to call function on jquery object that comes from jquery addon. js.Dynamic is special type that you can call any method on it, and compiler will just translate it to same call on js site (doc). You can understand it as "Trust me... there will be such method on runtime". Creator of jquery facade cannot assume what addons you would use, and you need to create your own facades or use it dynamically as shown above.

CALLING FUNCTION ON ELEMENT CREATED BY SCALATAGS

You can also create Modifier that will call your code when object is created. Be awared that it will be called before element is injected into dom:

import scalatags.JsDom.all._
import org.scalajs.jquery.jQuery
import org.scalajs.dom.Element
import scala.scalajs.js
import scalatags.JsDom.Modifier

val bootstrapTable:Modifier = new Modifier {
    override def applyTo(t0: Element): Unit = {
      val t = jQuery(t0)
      t.asInstanceOf[js.Dynamic].bootstrapTable()
      t.find("tr td:first-of-type").each(i => {
        ExtTable.callback(e)
      })
  }
//in scala 2.12 you can write it simpler
val bootstrapTable2:Modifier = (t0: Element) => {
      val t = jQuery(t0)
      ...
  }

div(p(
  id := "componentId", 
  "Html constructed here",
  //use modifier here,
  bootstrapTable,
  //you can create new anonymous modifier here: 
  new scalatags.JsDom.Modifier {
    override def applyTo(t: Element): Unit = println("ex1:" + t.outerHTML)
  }
  //in scala 2.12+ you can do it like that (same semantic as above example)
  (e:Element) => println("ex2:" +e.outerHTML)
)).render
  • Here you don't need to use jQuery("#" + componentId) because you have access to Element (jQuery(t0)).
  • scala SAM 2.12 makes it even simpler as you can see. You can create instance of Modifier using closure notation.
Sign up to request clarification or add additional context in comments.

10 Comments

I do not undestand how should I pass this to script() tag, which expects a Javascript code (a string). I cannot call this code from the place I am creating it, as this is where HTML is prepared, not executed. I want this to be executed from the HTML code, hence the script tag.
I have extended the question code a bit, so that the role of the script tag is clearer.
you can export function from scala code and then simply call it on js side, But personally I thing you have missed something. You should not need such weird constructs in your code (I'm using scalajs for ~3years daily and I've not used js even once in such manner). You should call this code probably after injecting div(...).render to dom. Not sure how and when you are doing it. You can ask udash guys on gitter also: gitter.im/UdashFramework/udash-core
"you have missed something". " after injecting ... to dom" ... What I was missing is that most of the time I can execute the code right on the render result, as this already returns a dom. The plugin scripts I have used so far manipulate it just fine, there is no need for the dom to be inserted into the window document.
render creates dom elements, But script tag is not executed until it is in document. Probably udash manages injecting into dom for you. I'm not udash user and don't know how they manage it in this framework but I'm sure that .render does not inject anything (it is scalatags method, and I'm using scalatags a lot). You still can use your script trick but you need to do that differently. (You can export function from scala code and then simply call it on js side). Hope that I've helped, if so please upvote or mark as answered.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.