[SOLVED] How to refer to `this` in Halogen

I’ve been doing workarounds like calling document.querySelector() via FFI, but this came up once and keeps coming back again, so I’m wondering isn’t there a proper solution?

Given the following dialog implementation taken from here:

<button onclick="this.nextElementSibling.showModal()">Test</button>

<dialog style="padding: 0" onclick="event.target==this && this.close()">
  <form style="padding: 1rem" method="dialog">
    <p>Click outside to close the dialog.</p>
    <button>Click here</button>
  </form>
</dialog>

I want to implement the onclick="event.target==this && this.close()" code.

Now, in Halogen design onClick triggers a handleAction where I’d have to execute the effects of event.target==this && this.close(). But inside handleAction the this refers to whole window and not to the component being rendered. So how you supposed to handle such reference?

Not sure if there is something to get this inbuild, but I’d go with RefLabels and just pass the label along as a first parameter to your Event/Handler. Then you can get the element from the label in HalogenM/handler via getRef or getHTMLElementRef (just bellow)

1 Like

I’m trying to use RefLabel but I can’t seem to replicate the expected HTML + JS code.

The TL;DR is I have this:

…
focusRef :: H.RefLabel
focusRef = H.RefLabel "mymodal"
…
      dialog :: H.ComponentHTML DialogAction () m
      dialog = HH.dialog [HP.style "padding: 0", HP.ref focusRef, HE.onClick \ev -> CloseDialog ev]
…
  handleAction :: DialogAction -> H.HalogenM DialogState DialogAction () Unit m Unit
  handleAction (CloseDialog ev) =
    H.getHTMLElementRef focusRef >>= traverse_ \elem -> H.liftEffect $ closeDialogIfItsTarget ev elem

…so basically, in dialog rendering I 1. assign it a ref and 2. assign a onClick that saves the event value.

Then during the click handling I call closeDialogIfItsTarget ev elem which is supposed to mimic the event.target==this && this.close() and looks like this:

FFI.js:

"use strict";
export const closeDialogIfItsTarget = function(mouseEvent, dialogRef) {
    return function () {
        if (mouseEvent.target === dialogRef)
            dialogRef.close();
    };
};

FFI.purs:

module FFI where

import Web.HTML.HTMLElement

import Effect (Effect)
import Prelude (Unit)
import Web.UIEvent.MouseEvent (MouseEvent)

foreign import closeDialogIfItsTarget :: MouseEvent -> HTMLElement -> Effect Unit

Well, the close() never gets executed. If I add console.log() of the parameters, the dialogRef is undefined! But it isn’t in the PureScript code! If I replace closeDialogIfItsTarget ev elem with closeDialogIfItsTarget ev (spy "dbg: " elem), I can perfectly see the elem has value :thinking:

Oh, I figured it out, it’s just that I didn’t know that multiple-params FFI functions should be embedded into one another due to carrying. So with this change it works as expected:

export const closeDialogIfItsTarget = function(mouseEvent) {
    return function (dialogRef) {
        return function () {
            if (mouseEvent.target === dialogRef)
                dialogRef.close();
        }
    };
};

So, to recap: this isn’t immediately available in handleAction, but you can add HP.ref … property to an element, and then retrieve it with H.getHTMLElementRef inside handleAction, which when passed via FFI will be same as this would.

And then, the event.target from the JS code can be extracted from the parameter to the onClick lambda. On the PureScript side target isn’t exported (I sent a PR), but if you pass this param via FFI, the target field will be there.

Regarding how to use refs, I was referred to this example which is what I used.

2 Likes