4

Is there a straightforward way to have a Cmd execute after the view is updated? In particular, I’m trying to reposition the cursor in a textarea upon specific keys (like the enter key) being pressed. In my update function I have:

case keyboardEvent.key of
    "Enter" ->
        ( modelAfterEnterPressed model keyboardEvent.selectionStart, setCursor model.cursor )
    "Tab" ->
        ....

My setCursor port is called and the corresponding JavaScript code calls the textarea‘s setSelectionRange function properly. And then Elm calls my view function which updates the textarea‘s content. Unfortunately, that wipes out my cursor position.

I need for the textarea‘s content to be updated before calling textarea.setSelectionRange() in my JavaScript port. Any way to do this without resorting to setTimeout which might not always work and can cause flashes within the timeout?

at.
  • 50,922
  • 104
  • 292
  • 461

1 Answers1

6

The standard way to do this is to trigger a command that returns in the next tick - e.g. Date.now - and then trigger the port command from that message. That guarantees that the view function will have run and your text area is present.

An alternative, which seems to work as well in my experience, is to replace a setTimeout (with an arbitrary interval) with a requestAnimationFrame:

elm.ports.selectText.subscribe( () => {
  requestAnimationFrame( () => {
    var textarea = document.querySelector(...);
    textarea.setSelectionRange();
  });
});

This will trigger after the view has been rendered too.

Simon H
  • 20,332
  • 14
  • 71
  • 128
  • Elm actually has a built-in subscription function for `requestAnimationFrame()`: http://package.elm-lang.org/packages/elm-lang/animation-frame/latest/AnimationFrame. My concern was that typed keys could be registered/processed between the first `KeyboardEvent` and the next animation frame. That would throw everything off. – at. Jun 09 '17 at 21:48