Creating custom DOM events with scalajs - scala

I can't find a way to create custom events with scala-js. For instance, with js you can create a custom event like the following (taken from here):
var event = new CustomEvent('build', { 'detail': elem.dataset.time });
However, there is no constructor for CustomerEvent or Event in scala-js that accept arguments. Also, subclassing either such as:
class DrawEvent extends Event {
override def `type` = "draw"
}
leads to
Uncaught TypeError: undefined is not a function
when trying to construct via new DrawEvent()
Any ideas?

To instantiate javascript classes in ScalaJs you have to use js.Dynamic.newInstance:
This should work for your use case:
val event = js.Dynamic.newInstance(js.Dynamic.global.CustomEvent)("build", js.Dynamic.literal(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]
There is more info available at the remarks portion (all the way at the bottom) of:
http://www.scala-js.org/doc/calling-javascript.html
Here is the same solution using some imports to make it shorter
import js.Dynamic.{ global => g, newInstance => jsnew, literal => lit }
val event = jsnew(g.CustomEvent)("build", lit(detail = elem.dataset.time)).asInstanceOf[js.dom.CustomEvent]

If you want to stay in the typed DOM (assuming you are talking about the scala-js-dom library), you can do:
new CustomEvent().initCustomEvent('build', false, false, elem.dataset.time)
The constructor you are using is actually only specified in DOM 4 (see MDN).

Related

#JSGlobalScope in scala.js 1.0 (JavaScriptException, ReferenceError, var is not defined)

After migrating from scala.js 0.6.x to 1.0, I've got some code related to #JSGlobalScope broken.
My use case is like this:
there's a 3rd-party library that expects some global var to be set to a function
when loaded and ready, it calls this function (by name)
I set this function in global scope from scala.js
The code looks like this:
#js.native
#JSGlobalScope
object Globals extends js.Object {
var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}
then I set this var like this:
Globals.callbackFunctionFor3rdPartyLib = () => {
// do things
}
and then I add the script into the DOM.
This was working with scala.js 0.6.x, but with 1.0 I'm getting an exception like the following:
scala.scalajs.js.JavaScriptException: ReferenceError: callbackFunctionFor3rdPartyLib is not defined
In the changelog for 1.0.0 there's a "Breaking changes" section that mentions this:
Accessing a member that is not declared causes a ReferenceError to be thrown
...
js.Dynamic.global.globalVarThatDoesNotExist = 42
would previously create said global variable. In Scala.js 1.x, it also throws a ReferenceError.
My question is:
what is the right way to do something like this (create a new global var) in scala.js 1.0?
If you know you'll always be in a browser context, you can use #JSGlobal("window") instead of #JSGlobalScope on your Globals, which will then be equivalent to doing window.myGlobalVarFor3rdPartyLib in JS. So that will work.
#js.native
#JSGlobal("window")
object Globals extends js.Object {
var callbackFunctionFor3rdPartyLib: js.Function0[Unit] = js.native
}
If not, but you are using a script (so not a CommonJS nor an ES module), the best thing is actually to use
object Globals {
#JSExportTopLevel("myGlobalVarFor3rdPartyLib")
var foo: js.Function[Unit] = ...
}
Note that Globals is a normal Scala object now, not a JS one.
The #JSExportTopLevel creates a top-level var myGlobalVarFor3rdPartyLib at the top of the script, and then assigning Globals.foo will also assign that top-level var.
If you're not using a script nor know that you're going to always be in a browser, then you need to figure out the global object yourself. Scala.js 0.6.x tried to do that for you, but could fail, so we don't do that anymore. You can at least follow the "instructions" on the documentation of js.special.fileLevelThis to reproduce what Scala.js 0.6.x was doing. I repeat the instructions here:
Using this value should be rare, and mostly limited to writing code
detecting what the global object is. For example, a typical detection
code--in case we do not need to worry of ES modules--looks like:
val globalObject = {
import js.Dynamic.{global => g}
if (js.typeOf(g.global) != "undefined" && (g.global.Object eq g.Object)) {
// Node.js environment detected
g.global
} else {
// In all other well-known environment, we can use the global `this`
js.special.fileLevelThis
}
}
Note that the above code is not comprehensive, as there can be JavaScript
environments where the global object cannot be fetched neither through
global nor this. If your code needs to run in such an environment, it
is up to you to use an appropriate detection procedure.

How to extend JavaScript HTMLElement class in ReasonML for web component?

For the following JavaScript code, how can I write it in ReasonML?
class HelloWorld extends HTMLElement {
constructor() {
super();
// Attach a shadow root to the element.
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `<p>hello world</p>`;
}
}
I could not find any documentation on writing classes in ReasonML? I cannot use plain objects/types as I need to extend from HTMLElement class which doesn't work with ES style classes.
I have looked into this existing question - How to extend JS class in ReasonML however, it is a different thing. To write web component, we need to extend HTMLElement and must call it with new keyword. ES5 style extension mechanism doesn't work.
You can't. Not directly at least, since BuckleScript (which Reason uses to compile to JavaScript) targets ES5 and therefore has no knowledge of ES6 classes.
Fortunately, ES6-classes require no special runtime support, but are implemented as just syntax sugar, which is why you can transpile ES6 to ES5 as shown in the question you link to. All you really have to do then, is to convert this transpiled output into ReasonML:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var BaseElement = (function (_super) {
__extends(BaseElement, _super);
function BaseElement() {
_super.call(this);
}
return BaseElement;
}(HTMLElement));
And depending on what specific class-features you actually need, you can probably simplify it a bit.

ScalaFX How do I create a method to react to changes in a var (ObjectProperty)?

I am making a multiplayer game client with ScalaFX GUI and Akka remoting for networking. When my client receives game data it stores it inside Model.gameData. I need my GUI to respond to this variable change.
I used gameData to create data:ObjectProperty in my Model object:
object Model {
var gameData:Option[GameData] = None
val data = new ObjectProperty(this,"data",Model.gameData)
...
}
drawGrid and drawPlayer are methods I use to update the GUI, located in CleintGUI object. I tired using addListener and onChange, they compile but the methods I placed inside of them are never invoked.
object ClientGUI extends JFXApp{
...
Model.data.addListener{ (o: javafx.beans.value.ObservableValue[_ <:Option[GameData]], oldVal: Option[GameData], newVal: Option[GameData]) =>
drawGrid
drawPlayer
}
Model.data onChange {
drawGrid
drawPlayer
}
}
What am I missing? Am I declaring data:ObectProperty or methods inside my ClientGUI incorrectly?
drawGrid and drawPlayer both work when I call them manually by creating an event through submitting a string in a TextField. When I receive GameData I also tried to directly call drawGrid and drawPlayer form inside of my actor class, but I got an error "Not an FX thread".
Edit: I got the GUI to update by mutating control attributes. However, ideally I would want to define the control attributes by using conditional expressions:
val data = new BooleanProperty(this,"data",Model.gameData.isDefined)
val msgLabel = new Label{
text <== when(data) choose " " otherwise "No GameData"
}
But this doesn't work as I can't figure out a way to define BooleanProperty such that when(data) changes value depending on boolean Model.gameData.isDefined
I was adding new elements to the GUI when I received gameData, by using GridPane.add method.
Instead of doing that I added all the controls(gui nodes/elements) during object creation and then changed their relevant attributes when I receive gameData.
e.g. I set Label.text from "No Game Data" to an empty string when I receive gameData:
def update {
ClientGUI.msgLabel = " "
}
I don't think this is the best approach as now I have publicly available vars in a multi threaded application, but since I only change them from one place when I receive new data it should be fine.
Ideally I would want to define the control attributes by using conditional expressions:
val data = new BooleanProperty(this,"data",Model.gameData.isDefined)
val msgLabel = new Label{
text <== when(data) choose " " otherwise "No GameData"
}
But this doesn't work as I can't figure out a way to define BooleanProperty such that when(data) changes value depending on boolean Model.gameData.isDefined

How to write new widgets implementing MVC with lablgtk2?

I am writing a family of new widgets for lablgtk2, the OCaml bindings for Gtk+. Some of these widgets can edit or present a fairly complex information, I am therefore interested in using model-view-controler or subject-observer, similar to what can be found in the GTree module.
This module defines a GTree.model and a GTree.view class, each having signals which can be connected to, and a GTree.model can be attached to one or more GTree.view's.
Imitating this organisation in pure OCaml is not that trivial, because the code available in the library is a binding of the C-library. I need to go through the following steps:
Defining new widgets
Defining new signals
Triggering these new signals
Defining new models
I could go through 1 and 2 but I am not sure how to do 3 and 4. How to do these right?
Defining new widgets
The definition of new widgets itself is not problematic. The new widget is typically a specialised version of the Gnome canvas or a composite. In the former case, our new widget can inherit from the Gnome canvas as a GObj.widget and in the latter case, we can use the GObj.widget provided by the container used to hold the composite. This typically looks like
class view () =
let vbox = GPack.vbox () in
…
object(self)
inherit GObj.widget vbox#as_widget
…
end
Defining new signals
The bindings give plenty of examples for code defining new signals, so that we can define new signals for our widgets, as illustrated by the following snippet, considering the simple case of signals without parameters:
open GtkSignal
module Event =
struct
let plop : ([>`widget], unit -> unit) t = {
name = "plop_event";
classe = `widget;
marshaller = marshal_unit;
}
let fizz : ([>`widget], unit -> unit) t = {
name = "fizz_event";
classe = `widget;
marshaller = marshal_unit;
}
end
class pill_signals obj =
object (self)
inherit ['a] GObj.gobject_signals (obj :> Gtk.widget Gobject.obj)
method plop = self#connect Event.plop
method fizz = self#connect Event.fizz
end
With these definitions, our view widget can expose these signals by defining an appropriate connect method:
method connect =
new pill_signals obj
Triggering the new signals
It seems that the function GtkSignal.emit serves the purpose of emitting a signal to an object, triggering the registered callbacks. This functions as the following signature:
val emit :
'a Gobject.obj ->
sgn:('a, 'b) GtkSignal.t ->
emitter:(cont:('c Gobject.data_set array -> 'd) -> 'b) ->
conv:(Gobject.g_value -> 'd) -> 'b
The first two parameters are self-explaining, but it is not that clear, what the two remaining ones are. Unfortunately, there is no use example in lablgtk source code, as signals are emitted from the C-side of the code. These two arguments seems to be related with the preparation of the arguments of the signal, materialised as a 'c Gobject.data_set array and the retrieval of the yielded value with the argument labeled ~conv. Nevertheless, the role of the ~cont-argument in the emitter still has to be cleared.
Defining the new model
The tricky part in the definition of the model, is that it should inherit from GObj.object in order to be able to send an receive signals. Unfortunately, there is no function allowing to directly define a minimal Gtk+ object. The farthest I went in this direction was
module Model =
struct
let create () =
GtkObject.make ~classe:"GObject" []
end
let model () =
new model (Model.create ())
Calling the function model to instantiate the corresponding object yields the message:
Gtk-CRITICAL **: IA__gtk_object_sink: assertion 'GTK_IS_OBJECT (object)' failed
Clearly, there is something fishy here, most probably the parameter list (the empty list in the snippet above) was too small.
LablGTK provides a nice interface to Gtk signaling mechanisms, which allows us to use it without tinkering with GtkSignal and marshalling functions. This interface is provided by GUtil and is neatly documented.
How to use GUtil, as described in the module documentation
To add ML signals to a LablGTK object:
{[
class mywidget_signals obj ~mysignal1 ~mysignal2 = object
inherit somewidget_signals obj
inherit add_ml_signals obj [mysignal1#disconnect; mysignal2#disconnect]
method mysignal1 = mysignal1#connect ~after
method mysignal2 = mysignal2#connect ~after
end
class mywidget obj = object (self)
inherit somewidget obj
val mysignal1 = new signal obj
val mysignal2 = new signal obj
method connect = new mywidget_signals obj ~mysignal1 ~mysignal2
method call1 = mysignal1#call
method call2 = mysignal2#call
end
]}
You can also add ML signals to an arbitrary object; just inherit from ml_signals in place of widget_signals and add_ml_signals.
{[
class mysignals ~mysignal1 ~mysignal2 = object
inherit ml_signals [mysignal1#disconnect; mysignal2#disconnect]
method mysignal1 = mysignal1#connect ~after
method mysignal2 = mysignal2#connect ~after
end
]}
It is now easy to address the points 1, 2, 3, and 4 above:
This is fine
Use GUtil to define new signals instead of GtkSignal
Triggering the new signals is accomplished by the call method of ['a] GUtil.signal.
Since we do not use GtkSignal anymore, there is actually no problem.

Cannot access the parameter of a Menu.param from a Lift Snippet

I'm trying to extract the parameter from a Lift Menu.param within a snippet so that I can use it to create a named Comet. However, I get a NullPointerException when I try to pass the parameter to the snippet using SnippetDisptach in my Boot.scala, as suggested here:
http://comments.gmane.org/gmane.comp.web.lift/44299
I've created the Menu item as follows:
object AnItemPage {
// create a parameterized page
def menu = Menu.param[Item]("Item", "Item",
s => fetchItem(s), item => item._id.toString) / "item"
private def fetchItem(s:String) : Box[Item] = synchronized {
ItemDAO.findById(ObjectId.massageToObjectId(s))
}
}
I've added the menu to SiteMap. I've also created a Snippet which I would like to pick up the Item parameter. (I'm using fmpwizard's InsertNamedComet library here):
class AddCometItemPage(boxedItem: Box[Item]) extends InsertNamedComet with DispatchSnippet{
val item : Item = boxedItem.openOr(null)
override lazy val name= "comet_item_" + item._id.toString
override lazy val cometClass= "UserItemCometActor"
def dispatch = null
}
My next step is to crate an instance of this class as demonstrated by David Pollak here:
http://comments.gmane.org/gmane.comp.web.lift/44299
This is what I have added to my Boot.scala:
LiftRules.snippetDispatch.append {
case "item_page" => new AddCometItemPage(AnItemPage.menu.currentValue)
}
My item.html references this snippet:
<div class="lift:item_page">
I get the following null pointer exception when I compile and run this:
Exception occurred while processing /item/5114eb4044ae953cf863b786
Message: java.lang.NullPointerException
net.liftweb.sitemap.Loc$class.siteMap(Loc.scala:147)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.siteMap(Menu.scala:170)
net.liftweb.sitemap.Loc$class.allParams(Loc.scala:123)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.allParams(Menu.scala:170)
net.liftweb.sitemap.Loc$class.net$liftweb$sitemap$Loc$$staticValue(Loc.scala:87)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.net$liftweb$sitemap$Loc$$staticValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.sitemap.Loc$$anonfun$paramValue$2.apply(Loc.scala:85)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.paramValue(Loc.scala:85)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.paramValue(Menu.scala:170)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.sitemap.Loc$$anonfun$currentValue$3.apply(Loc.scala:114)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.sitemap.Loc$class.currentValue(Loc.scala:114)
net.liftweb.sitemap.Menu$ParamMenuable$$anon$9.currentValue(Menu.scala:170)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:107)
bootstrap.liftweb.Boot$$anonfun$lift$8.apply(Boot.scala:106)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.util.NamedPF$$anonfun$applyBox$1.apply(NamedPartialFunction.scala:97)
net.liftweb.common.Full.map(Box.scala:553)
net.liftweb.util.NamedPF$.applyBox(NamedPartialFunction.scala:97)
net.liftweb.http.LiftRules.snippet(LiftRules.scala:711)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.http.LiftSession$$anonfun$net$liftweb$http$LiftSession$$findSnippetInstance$1.apply(LiftSession.scala:1506)
net.liftweb.common.EmptyBox.or(Box.scala:646)
net.liftweb.http.LiftSession.net$liftweb$http$LiftSession$$findSnippetInstance(LiftSession.scala:1505)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1670)
net.liftweb.http.LiftSession$$anonfun$locateAndCacheSnippet$1$1$$anonfun$apply$88.apply(LiftSession.scala:1669)
Has anybody any idea where I'm going wrong? I've not been able to find a lot of information on Menu.param.
Thank you very much for your help.
f
I have never tried what you are doing, so I am not sure the best way to accomplish it. The way you are using the Loc Param, you are extracting a variable from a URL pattern. In your case, http://server/item/ITEMID where ITEMID is the string representation of an Item, and which is the value that gets passed to the fetchItem function. The function call will not have a value if you just arbitrarily call it, and from what I can see you are requesting a value that is not initialized.
I would think there are two possible solutions. The first would be to use S.location instead of AnItemPage.menu.currentValue. It will return a Box[Loc[Any]] representing the Loc that is currently being accessed (with the parameters set). You can use that Loc to retrive currentValue and set your parameter.
The other option would be to instantiate the actor in your snippet. Something like this:
item.html
<div data-lift="AnItemPage">
<div id="mycomet"></div>
</div>
And then in your AnItemPage snippet, something like this:
class AnItemPage(item: Item) {
def render = "#mycomet" #> new AddCometItemPage(item).render
}
I haven't tested either of those, so they'll probably need some tweaking. Hopefully it will give you a general idea.