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

A Promise for running logic after an Element's attributes have been constructed. #18

Open
trusktr opened this issue Oct 24, 2017 · 9 comments

Comments

@trusktr
Copy link

trusktr commented Oct 24, 2017

This DOM issue is giving me a hard time, because I'm trying to detect attributes on custom element construction so that I can detect side effects of custom attributes, and I can only get it to work with a setTimeout(..., 0) (has to be a macrotask).

So I have something like

    constructor() {
        super()
        setTimeout( () => this.doSomethingWithPreExistingAttributes(), 0 )
    }

I see that in custom-attributes, the MutationObserver allows us to detect when an element finally has attributes.

I can probably replace my setTimeout with my own MutationObserver, but maybe it'd be nice to have a Promise-based API for detecting custom attributes instances, which is what I'm interested in, so that not only are the attributes existing, but the custom attributes classes surely instantiated.

API might look like

customAttributes.whenReady(element).then(...)

Inside a Custom Element constructor, this could be like

customAttributes.whenReady(this).then(...)

so at this point I know

  1. Attributes exist (thanks, custom-attributes already has a MutationObserver, no need to make another one).
  2. Custom Attributes are already instantiated and possible initial sideeffects are already completed. Currently it may be possible to register a MutationObserver on an element before the one in custom-attributes, which will mean we will know when attributes are ready, but will have to still write a deferral hack to ensure that Custom Attributes are constructed.

What do you think?

@matthewp
Copy link
Owner

I'm not totally against this, would want to see how practical it is given the implementation.

@matthewp
Copy link
Owner

Would you care this this promise would never resolve if there are no custom-attributes (only regular attributes)?

@matthewp
Copy link
Owner

matthewp commented Oct 25, 2017

As I think about it, I'm starting to lean against. I see the value in a way to know if an element's attributes are "ready". However I would want to know that for any type of attributes. So I'm not sure if this functionality is a good fit in this library. Something that you are looking for could be written as such (pseudo-code, untested):

function attributesReady(element) {
  let doc = element.ownerDocument;
  if(doc.contains(element)) {
    return Promise.resolve();
  } else {
    return new Promise((resolve) => {
      let mo = new MutationObserver(mutations => {
        for(let mutation of mutations) {
          for(let addedNode of mutation.addedNodes) {
            if(addedNode === element) {
              mo.disconnect();
              resolve();
            }
          }
        }
      });
      mo.observe(doc.documentElement, {
        childList: true,
        subtree: true
      });
    });

  }
}

class MyElement extends HTMLElement {
  constructor() {
    super();
    
    attributesReady(this).then(() => {
      // Do whatever
    })
  }
}

@trusktr
Copy link
Author

trusktr commented Oct 25, 2017

I wrote an example too, let me post when I get on laptop. We would also need to consider that custom attributes can be defined later, asynchronously, at any point later after initial document parsing, so

  if(doc.contains(element)) {
    return Promise.resolve();
  }

would be valid only for some cases. I think the best way might be to do it in the initial observation of an element after all _found calls complete for all of the element's attributes.

@trusktr
Copy link
Author

trusktr commented Oct 25, 2017

I meant,

  if(doc.contains(element)) {
    return Promise.resolve();
  }

would surely tell attributes are ready for the element, but not necessarily Custom Attribute instances.

But maybe only the initial attributes during doc parsing matter? Still finding out...

@trusktr
Copy link
Author

trusktr commented Oct 26, 2017

Here's an initial implementation but not quite working yet: master...trusktr:attribute-ready-promise

It's on top of the changes I made in #17, so you'll see those in the diff too.

Usage is

customAttributes.whenReady(someElement, 'some-attribute').then(...)

but it is for specific attributes. Note that the promise resolution happens immediately in whenReady if the attribute instance is already created, otherwise it resolves in _found after the instance is created.

It'd be great to have a generic promise as previously described, and that would be like:

customAttributes.whenReady(someElement).then(() => {
  // any attributes that were already defined and written on the element will have been created.
  // Possibly none were created if none were defined or not written on the element.
})

I'll post an update when tested and when I also make the generic one.

I'd describe the API something like this:

Call whenReady(element, attribute) when you want to wait for a specific custom attribute to have been created. This promise can be used any time. If the custom attribute instance is already created, the promise will resolve immediately, otherwise the promise will resolve once the attribute is both defined and has been instantiated onto the element.

Call whenReady(element) when you want to wait for any possibly-defined custom attributes to have been created. After this Promise resolves, all initially defined custom attributes will have been created, but it is also possible that no custom attributes were created if they were not yet defined. This promise can only be used one time during the life of an element.

@trusktr
Copy link
Author

trusktr commented Oct 26, 2017

Interestingly, I've used your custom-attributes to implement a generic has="" attribute that I'm calling "element-behaviors" which I'll publish at https://github.com/trusktr/element-behaviors soon (original idea here).

@trusktr
Copy link
Author

trusktr commented Jan 26, 2019

Hello Matthew, I've stumbled back here thinking about if I need this sort of thing again.

The reason is, if I am making a custom element, and the custom element is shipped with some custom attributes, then

  • I need to wait for the element to load part of a certain feature in connectedCallback,
  • I need to wait for the element's custom attributes to finishing loading part of the feature in connectedCallbacks, and
  • Finally, I'd like to emit an event to tell the user of the custom element when certain feature has finished loading (including both the part loaded in the custom element and the part loaded in the custom attribute, they both handle different responsibilities of the feature).

This is quite complicated actually, because for example, customElements.define('the-element-i-am-talking-about') could be called in arbitrary order, even after custom attributes are defined (because I let the user define the element names).


I know there's ways to make this work, for example I could tightly couple my elements to my attributes, and make either the element call methods on the attributes, or the attributes call a method on the element, depending on load order (f.e. depending on customElements.define() order), but it will make the code uglier I feel like.

If I find some pattern, I'll post back.

@matthewp
Copy link
Owner

I'm warming up to whenReady now. I think it should take an attribute name as an argument though. Attributes could be ready at different points in time.

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

No branches or pull requests

2 participants