OvermindDL1 / bucklescript-tea

TEA for Bucklescript

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Problem with pushMsg?

playfulThinking opened this issue · comments

Perhaps I don't I understand how to use pushMsg. I'm extending the starter project like this:

in index.html:

 buckle_app = starter.main(document.body);

I've added the msg AnimationUpdate to model

in Main:

open Tea_program (* needed for pushing messages into the app from JS *)
...
external pushMsg : 'a Tea_program.processMsg -> unit = "" [@@bs.val]
external buckle_app : 'a = "buckle_app" [@@bs.val]
let update_handler () = 
  let () = buckle_app##pushMsg (PushMsg `AnimationUpdate) in ()

When my update_handler gets called from JS, (thus calling pushMsg) the model gets set to some large negative integer.

In debugging this, I found out that

type msg =
  | AnimationUpdate
...
  [@@bs.deriving {accessors}]

generates an int for the accessor, rather than a function.

If instead, I have
``
type msg =
| AnimationUpdate of int


then a real accessor is generated:

function animationUpdate(param_0) {
return /* AnimationUpdate */__(0, [param_0]);
}


 **and** my model doesn't get trashed (with suitable changes to references to AnimationUpdate)!

Am I doing something wrong, or is there a bug somewhere?

PS. Thanks so much for your work, it's fantastic!

pushMsg is very low level stuff that should usually not be touched unless building other API's (like Cmd's and Sub's and Event's use it) first of all. ^.^;

Second:

let () = buckle_app##pushMsg (PushMsg `AnimationUpdate) in ()

And:

type msg =
  | AnimationUpdate

Notice:

`AnimationUpdate !== AnimationUpdate

The left is a polymorphic variant, the right is your variant. They do not have the same values and you are likely to crash hard. I'm not sure how the given example would compile actually? o.O

generates an int for the accessor, rather than a function.

Correct, body-less variants are just integers internally. That is how OCaml works. ^.^

open Tea_program (* needed for pushing messages into the app from JS *)
...
external pushMsg : 'a Tea_program.processMsg -> unit = "" [@@bs.val]
external buckle_app : 'a = "buckle_app" [@@bs.val]
let update_handler () = 
  let () = buckle_app##pushMsg (PushMsg `AnimationUpdate) in ()

This seems very... odd. I'm not sure what PushMsg is at all there, I'm not seeing it elsewhere in your displayed code?

Am I doing something wrong, or is there a bug somewhere?

I'd wager it is just being used wrong. ^.^;

You really should pass information from javascript into the app at load-time via flags, or at runtime later via stock-standard HTML events or by making a new Subscription type (more work).

What precisely are you trying to accomplish? Can you make a minimal reproducible example?

Thanks very much for your fast and extended response. I must admit I'm a newbie at this, so I didn't know you could make custom browser events - that's why I tried using pushMsg!

About:

  let () = buckle_app##pushMsg (PushMsg `AnimationUpdate) in ()

Embarrassed sigh, now fixed:

  let () = buckle_app##pushMsg AnimationUpdate in ()

...and it works.

I'd like to take your advice re using standard HTML events instead of pushMsg.

What I'm trying to accomplish is using the GSAP JS animation library in conjunction with bucklescript-tea. GSAP doesn't know how to use a virtual DOM, so I have GSAP make changes to a JS object instead, and then I then reflect the changes into the view.

GSAP can't generate HTML events, but you can have GSAP notify you when it makes changes.
Here's my callback in index.html:

      function jsAnimationUpdate(){
        console.log("in jsAnimationUpdate")
        var event = new Event("animationUpdate");
        document.documentElement.dispatchEvent(event);
      }

and my view has:

div
    [onMsg "animationUpdate" AnimationUpdate]
...

update has this:

| AnimationUpdate -> 
    let () = Js.log "in AnimationUpdate" in
    model

jsAnimationUpdate is getting called by GSAP; but no AnimationUpdate messages are being generated, and I'm stumped. Surely I'm missing something simple!

Embarrassed sigh, now fixed:

We all experience that! ^.^

I'd like to take your advice re using standard HTML events instead of pushMsg.

Yeah the webstandard 'componantized' way is to pass information 'into' things via DOM events, the basics of which is like:

On your javascript side:

var event = document.createEvent('some_event');
baseVdomElem.dispatchEvent(event);

And you can put custom data on the event by using a custom event too, make sure to put it on the first child element of the vdom for example. You can listen to it in your app just like any other event like a click event or whatever, except with whatever name you specified (use onMsg for simple no-data messages, or use onCB with a key name to parse out data from the event to return with a message), so for the above:

let view model =
  div
  [ onMsg "some_event" AnimationUpdate
  ; ...otherstuff
  ]
  [ ... yourelements
  ]

So basically how you are doing it, except you want to raise the event on the vdom node instead of the document, or you can raise it on the document but be certain to set bubbles on the event itself to true (default false on the generic event as I recall), but be warned that it will bubble through every-single-element on the page, so if multiple things listen then multiple things will trigger, even if nothing does it is still slow due to checking the entire DOM.

Another way would be to instead of listen for the event on the vdom, instead do it via a subscription. Right now Tea.Mouse has a nice registerGlobal function that returns a subscription to listen to events on the root document, which is probably what you want if you are putting the event on the document, and if so then instead of doing anything special to the view then put something like this in your subscription instead:

let subscriptions model =
  Tea.Mouse.registerGlobal "some_event" "aUniqueKey" (fun _event -> Some AnimationUpdate)

The last parameter is a function that takes the event, you can parse data out of it or just return something like I did above (returning None is possible to do nothing too).

I should probably move registerGlobal to a Global module now that I'm thinking about it, it is generically useful and not just for mouse stuff. I'll do that but I'll keep a bouncer in mouse for backwards compat so feel free to use it for now. ^.^

I have plans of a webstandard Port-like api that I need to get written, it will make this fairly obvious to use at that point, and unlike Elm it will only use web standard methods, thus should work with anything else (could embed it in, say, a Vue app if you wanted then).

But yeah, overall your's was not working because you have bubbles set to false (as is default) so nothing on the DOM was hearing it. A global registration is best most likely, or on the vdom of something specific. :-)

Oh. Wow. A whole tutorial!

Thank you thank you thank you.

I will work on this this weekend! (and I hope you have a great one)

I will work on this this weekend! (and I hope you have a great one)

Lol, you too, enjoy! ^.^

I tried and failed to use Tea.Mouse.registerGlobal. I've made a minimal example at https://github.com/fremontmj/events-problem

It:

  • makes a new CustomEvent on the DOM with "bubbles" true
  • uses registerGlobal

I changed your example

let subscriptions model =
  Tea.Mouse.registerGlobal "some_event" "aUniqueKey" (fun _event -> Some AnimationUpdate)

to

let subscriptions model =
  Tea.Mouse.registerGlobal "animationUpdate" "aUniqueKey" (fun _event -> AnimationUpdate)

("Some AnimationUpdate" generated a type error)

I know my little jsAnimationUpdate method in index.html generates events on the DOM, I was able to capture them with a JS event handler (which I of course removed after confirming it was generating events). Try as I might, though, the events don't seem to get picked up in Tea.

Might registerGlobal only listen for events on the vdom and not the DOM?

If THAT is the problem, then in your other example,

var event = document.createEvent('some_event');
baseVdomElem.dispatchEvent(event);

how does one get a reference to a Vdom element in Javascript?

("Some AnimationUpdate" generated a type error)

Ah, I'll need to make that more generic later in that case. ^.^

Also it won't work! It is very mouse specific, it requires a pageX and pageY fields on it!!! I missed that! Such a generic version will obviously not have that. ^.^;

Add pageX and pageY fields to the custom event you are throwing and it should work. My bad. ^.^

Guess I should make a global module. Elm does not have one built in but I think there is a package about it, I should perhaps try to mimic it's API, hmm...

https://github.com/fremontmj/events-problem

Yeesh, no build system? o.O
Making one, testing. ^.^

And it works after adding these two lines before your document.documentElement.dispatchEvent(event); line:

        event.pageX = 0;
        event.pageY = 0;

I get in console:

hello from jsAnimationUpdate
in subscription
got AnimationUpdate message

I really should setup a global registration subscription, it would be useful for a lot of things. ^.^

I'll leave this open to remind me. If you need it soon, keep pinging me (the more often I get pinged the more likely I am to see it when I have a few free moments to do something). :-)

how does one get a reference to a Vdom element in Javascript?

The VDom becomes real on the next animation frame tick, so just use normal DOM handling things then. Just remember that it might 'change' underneath you depending on how you build your VDOM. ^.^

Hmm, in my global subscription I should probably allow it to handle events on the parent node too, hmm... (Note left here to remind me...)

But yes, very sorry, the registerGlobal in mouse is a bit mouse specific in that it requires a pageX/pageY keys on the event object (same as is on the MouseEvent object). ^.^;

Also, bubbles can be false (which makes it much more performant) when registering and listening globally. :-)
If you disable bubbling then make sure to dispatch from document, not the documentElement, I.E. change your dispatch event line to document.dispatchEvent(event); if you set bubbles to false. :-)

Also it won't work! It is very mouse specific, it requires a pageX and pageY fields on it!!! I missed that! Such a generic version will obviously not have that. ^.^;

LOL

Got it working now...