Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOM side effects are difficult (focus, scrollTop...) #49

Closed
slorber opened this issue Nov 28, 2015 · 6 comments
Closed

DOM side effects are difficult (focus, scrollTop...) #49

slorber opened this issue Nov 28, 2015 · 6 comments

Comments

@slorber
Copy link

slorber commented Nov 28, 2015

Hi,

I understand the beauty of the ELM model but in practice it seems to me complicated to perform certain tasks, like giving the focus to an input just after insertion in the DOM.

In React one would just plug that effect in componentDidMount or componentDidUpdate and it is very easy and idiomatic.

I've found that the TodoMVC ELM example gives focus to the text input on todo edit and use a port to do the side-effect:

port focus : Signal String
port focus =
    let needsFocus act =
            case act of
              EditingTask id bool -> bool
              _ -> False

        toSelector act =
            case act of
              EditingTask id _ -> "#todo-" ++ toString id
              _ -> ""
    in
        actions.signal
          |> Signal.filter needsFocus (EditingTask 0 True)
          |> Signal.map toSelector
todomvc.ports.focus.subscribe(function(selector) {
    setTimeout(function() {
        var nodes = document.querySelectorAll(selector);
        if (nodes.length === 1 && document.activeElement !== nodes[0]) {
            nodes[0].focus()
        }
    }, 50);
});

However I think this solution is much complicated compared to react, and not very good :)

The usage of a CSS selector like #todo-id to me breaks a bit the ability to nest things. Using an id-based selector means we are using a global namespace. Using class-based selector means we can't easily mount twice the same component with the same class in the page.
If ELM is really nestable, I should be able to render the same app in 2 different containers in the page and the dom effects should be targeted to the appropriate app automatically.

Also the port makes use of setTimeout. I guess it is because we have to make sure we don't run the effect before the dom node is inserted, but this random 50 value comes from nowhere and could even lead to weird effect concurrency issues on real-world applications.

Same kind of problem: give scroll position to an element, or to load an external library... These are very common needs but the port system does not seem enough for me to solve that elegantly.

So, is there a better way to perform this kind of DOM effect?
I understand that exposing the dom node after mount like React does somehow breaks the functional purity model of ELM.

Any idea?

@slorber
Copy link
Author

slorber commented Jan 30, 2016

It seems CycleJS handles that with a callback put on the virtual DOM

    input('.edit', {
        type: 'text',
        value: propHook(element => {
          element.value = title;
          if (editing) {
            element.focus();
            element.selectionStart = element.value.length;
          }
        })
      })

See also cyclejs/cyclejs#153

It would be nice to be able to attach a port function to node mounting/updates/unmounting no? Even if the IO code is not done by Elm itself but in ports it could allow us to integrate more easily with existing libraries.

It would be nice if Elm supported in a good way VDom hooks

@OliverJAsh
Copy link

I agree with all of this! Especially:

Also the port makes use of setTimeout. I guess it is because we have to make sure we don't run the effect before the dom node is inserted, but this random 50 value comes from nowhere and could even lead to weird effect concurrency issues on real-world applications.

The random value is a big deal breaker for the application I'm working on, which requires me to constantly manage focus state.

@vito
Copy link

vito commented May 19, 2016

I've run into this same issue; it makes things like autoscrolling to the bottom as text appears very difficult (think chat logs or console output in a CI system). Right now we just scroll down every 100ms which looks super janky.

@evancz
Copy link
Owner

evancz commented Sep 4, 2016

http://package.elm-lang.org/packages/elm-lang/dom/latest

This is now used in the https://github.com/evancz/elm-todomvc example if you want to see it in action.

@evancz evancz closed this as completed Sep 4, 2016
@therealmarv
Copy link

therealmarv commented Sep 4, 2016

This is great news. Thanks @evancz !
I have only one question with (I admit) a messy approach:
In React I can use componentDidMount to also attach e.g. a good jQuery UI dialog directly to the DOM without breaking stuff (small video example here).

My question:
Is this doable with Elm and the new dom package?

@evancz
Copy link
Owner

evancz commented Sep 4, 2016

Best to ask questions like this on elm-discuss or the Elm slack. The issues are more for tracking bugs and work. Folks are friendly and can help you with your particular thing way better than me there!

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

No branches or pull requests

5 participants