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

Reintroduce virtual-dom hooks to allow deterministic DOM access from Javascript #11

Closed
tolgap opened this issue May 13, 2016 · 3 comments

Comments

@tolgap
Copy link

tolgap commented May 13, 2016

Recent changes

With the change from 0.16 to 0.17, elm-lang/html now depends on this repo which is not based on Matt-Esch/virtual-dom anymore. The current implementation of this virtual DOM does not have support for hooks anymore: https://github.com/Matt-Esch/virtual-dom/blob/master/docs/hooks.md

The hooks allow us to invoke some Javascript once the VNode has been flushed to the DOM. This would include things like Leaflet markers on a Openstreet map, WYSIWYG editors, or whatever. That's the nice part, it gives us more fine-grained access to the DOM once we decide to go Native for a piece of functionality.

Issue

Not having a way to know whether a VNode has been flushed to the DOM, causes us to write things like setTimeout(tryUntilElementVisible(var times = 5), 50) etc. If it's about form inputs, you could use onFocus, but that's not meant for non-form stuff. Sometimes you want to do something as soon as the element is visible, like attaching a WYSIWYG editor.

Proposal

It would be nice if we could use "something" like hooks from Matt-Esch/virtual-dom. This is where the magic happens. It checks whether the property is a hook, and if so, it calls the prototype method hook on it.

We could add a method Native.VirtualDom.hook that adds a hook property to our VNode. This would allow us to opt into adding functionality once our element is flushed to the DOM, and it helps us to kill the pesky setTimeout stuff we need to do otherwise.

Lets discuss!

@ssboisen
Copy link

I'm very much in favour of adding lifecycle/hooks to elm-html as it is a hard requirement for our adoption of Elm. A simple example is being able to set the focus of a dom-element after it have been mounted or updated, many more scenarios that require knowledge of updates, addition and removal of dom notes exist. What do you think @evancz ?

@ccapndave
Copy link

ccapndave commented May 17, 2016

I totally agree, but in the meantime here is a workaround which doesn't involve any nasty timeouts. It assumes that you are calling a function updateElement from an Elm port which expects to find an element in the DOM in order to do whatever it has to do. It also assumes that you are passing a single parameter to the function, which includes id which is the id of the element that should be added.

This example queues up the function calls so it can invoke them when the element becomes available, but you could as well call an incoming port in your application or anything like that. You can also cache observers, etc, etc

function updateElement({ id }) {
  const element = document.getElementById(id);

  if (element) {
    // Do your stuff
  } else {
    callWhenIdAdded(document.getElementById("app"), id, () => updateElement.apply(null, arguments));
  }
}

function callWhenIdAdded(parent: Element, id: String, fun: Function): void {
  const observer = new MutationObserver((mutations) => {
    // Get an array of the added ids
    const addedIds = mutations.reduce((acc, mutationRecord) => {
      const ids = Array
        .from(mutationRecord.addedNodes)
        .filter(node => node.attributes.getNamedItem('id') !== null)
        .map(node => node.attributes.getNamedItem('id').value);

      return acc.concat(ids);
    }, []);

    // If that contains the id we are looking for then invoke the function and remove the observer
    if (addedIds.indexOf(id) >= 0) {
      observer.disconnect();
      fun();
    }
  });

  observer.observe(parent, {
    childList: true,
    subtree: true
  });
}

P.S. If this does get called multiple times on the same element I don't think there is any guarantee that the function calls will happen in the same order as they were called, so if that matters for your use case you might need to add some logic.

@evancz
Copy link
Member

evancz commented Jul 13, 2016

I think it makes sense to go more in this direction and that is being tracked at https://github.com/elm-lang/html/issues/53

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

4 participants