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] = _
}
Suma
  • 33,181
  • 16
  • 123
  • 191

1 Answers1

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.
Scalway
  • 1,633
  • 10
  • 18
  • 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. – Suma Feb 28 '20 at 17:29
  • I have extended the question code a bit, so that the role of the `script` tag is clearer. – Suma Feb 28 '20 at 17:33
  • 1
    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: https://gitter.im/UdashFramework/udash-core – Scalway Feb 28 '20 at 17:57
  • "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. – Suma Feb 29 '20 at 21:10
  • `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. – Scalway Feb 29 '20 at 22:19
  • there is also https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver that could help. Maybe not simplest sollution but yea... should work well. – Scalway Feb 29 '20 at 22:23
  • "But script tag is not executed until it is in document" I agree. My point is that I do not have to use the script tag, as it is no problem to call the function even before it is in document. As for the upvote / accept, I understand you expect some reward for your effort. Your comments certainly helped me to avoid the question completely, but as far as I understand it the answer does not answer the question as it was asked. – Suma Mar 01 '20 at 00:21
  • Yea... I though that semantic of such script (calling this function when element is in dom) is important for You. There are libraries that will not work on detached elements but if it is no case here, answer should be much simpler. I've edited my answer, see last part. – Scalway Mar 01 '20 at 07:35
  • Scala's questions has terrible rewards to be hones. It is much simpler for me to answer questions from php/python/linux even if my knowledge there is much worse. – Scalway Mar 01 '20 at 07:52
  • It has helped me to avoid the question (which is why I have upvoted it), but I am not convinced it answered it, which is why I hesitate to accept it. – Suma Mar 03 '20 at 13:00