Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

onFocusMount #6

Open
mathieuleclaire opened this issue Nov 25, 2020 · 6 comments
Open

onFocusMount #6

mathieuleclaire opened this issue Nov 25, 2020 · 6 comments

Comments

@mathieuleclaire
Copy link

Hi,
I am tryiing to reproduce Laminar Hello World example, but the onMountFocus raises a js exception. Is it an exepected behaviour ?

val component = div(
      Textfield(
        _ => onMountFocus,
        _ => value <-- actionVar.signal,
        _ => inContext { thisNode => onInput.mapTo(thisNode.ref.value) --> actionVar },
        _.label := "Name",
        _.outlined := true,
        _.placeholder := "Name"
      ),
      span(
        "Hello, ",
        child.text <-- actionVar.signal.map(_.toUpperCase)
      )
    )

raises the js exception:

ObserverError: TypeError: this.formElement is null

Thanks, and congrats for this work !

@raquo
Copy link

raquo commented Nov 25, 2020 via email

@mathieuleclaire
Copy link
Author

It is the TextField wrapped in this library: https://github.com/uosis/laminar-web-components/blob/master/material/src/main/scala/material.scala#L3340
The debugger indicates that an input is produced:

<input aria-labelledby="label" class="mdc-text-field__input" type="text" placeholder="Name">

@raquo
Copy link

raquo commented Nov 26, 2020

Laminar's onMountFocus correctly calls customElement.focus() after the element is mounted. Specifically, I think that would be this method for this web component: https://github.com/material-components/material-components-web-components/blob/4a5d4eebd1ecf1c54515f7b42c7ec882d4d83470/packages/textfield/mwc-textfield-base.ts#L234

Why this web component's formElement is null at this point, I don't know. Maybe it needs more time to initialize after being mounted or something. I don't know much about how web components are implemented.

@mathieuleclaire
Copy link
Author

You're right, a 1ms delay does the trick.

_ => onMountCallback(ctx => {
          js.timers.setTimeout(1) {
            ctx.thisNode.ref.focus()
          }
        })

As, it seems to be a recurent issue (like Material UI and Progresse bar here: https://laminar.dev/examples/web-components), what do you think of proposing timer wrappers for all your onMount methods ? The onMountFocus would become:

val onMountFocus: Modifier[HtmlElement] = onMountFocusAfter(0)

def onMountFocusAfter(delay: Int = 0): Modifier[HtmlElement] = onMountCallback(ctx => {
      js.timers.setTimeout(delay) {
        ctx.thisNode.ref.focus()
      }
    })

Or to set a timeout(1) directly in your mount methods, whatever the component used.

@raquo
Copy link

raquo commented Nov 26, 2020

You should probably wrap the focus() call into if (ChildNode.isNodeMounted(ctx.thisNode)) to mitigate the edge case when the component will be unmounted before it has a chance to focus itself. Stuff like this is why I don't like async APIs.

If I were to add these async helpers into Laminar, I'd need a good reason – it's one thing if web components can't possibly be implemented synchronously due to some web platform constraints (and if that's the case, I would need to know what exactly those constraints are so that any potential Laminar helpers would solve the problem in all cases), but if it's just this particular set of web components that are internally poorly implemented, then I'm less inclined to develop ad-hoc helpers for that.

@uosis
Copy link
Owner

uosis commented Dec 15, 2020

Yeah web components are asynchronous by design - there is WebComponentsReady event, similar to DOMReady, that you need to wait for before you can call methods on web components. See here and here for examples.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants