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

[idea] slottedCallback for Custom Elements #527

Closed
trusktr opened this issue Jun 25, 2016 · 10 comments
Closed

[idea] slottedCallback for Custom Elements #527

trusktr opened this issue Jun 25, 2016 · 10 comments

Comments

@trusktr
Copy link
Contributor

trusktr commented Jun 25, 2016

There's currently no way for an HTMLElement to know when it gets distributed into a content or slot elements. In v1 API, there is a new slotchange event, but this is only useful to the parent of the slot element. In a closed tree, children can get references to the slot they are distributed into.

So how can an element know when it is distributed?

Maybe there can be a distributedCallback that Custom Element classes can define. The distributedCallbackcan receive a single argument: slot. The slot argument is a reference to the slot where the element got distributed into. If the tree that the element was distributed into is closed, then the value of the slot argument can be null. Additionally, slotchange handlers on the associated slot element should be executed before distributedCallbacks.

For example:

class MyElement extends HTMLElement {
  distributedCallback(slot) {
    console.log(slot)
  }
}

customElements.define('my-element', MyElement)

If it is a guarantee that slotchange event handlers on the receiving slot element fire before distributedCallback of the elements being distributed, then we can do some nifty things. For example, it would solve the problem I have in #504 (comment).

In my specific case (and I can see this also applying to any other libraries made of Custom Elements like http://aframe.io), I need to detect when element of type A is distributed into another element of type A, and I want to initiate an error state whenever an element of type A is distributed into an element that is not of type A.

My specific problem: if parent element A contains a slot element, then A can watch for slotchange events in order to detect distributed child elements and can check that those elements are also of type A. The parent A element can then establish a connection with the child A element. However, I need to detect the case where a child element A is distributed into a slot whose parent is not of type A, and I want to throw an error. This is currently not possible because the child element A does not know that it has been distributed, and even if it did, it cannot see the parent of the slot element if the shadow tree is closed.

If we can guarantee that slotchange fires before child distributeCallbacks, then in the distributedCallback of a child of type A that was distributed into any other element that is not of type A, the child A element will notice that a connection has not been made (parent would make the connection in a slotchange handler), and the distributed child can then make the safe assumption that it has not been distributed into another element of type A, and an error can be thrown.

@trusktr trusktr changed the title [idea] distributedCallback [idea] distributedCallback for Custom Elements Jun 25, 2016
@trusktr
Copy link
Contributor Author

trusktr commented Jun 28, 2016

This callback for Custom Elements could be a possible way to explain number 3 of #184 (comment), if that route is taken: the iframe element simply detects that it has been distributed using distributedCallback and in that case will decide not to affect the window's history.

@trusktr
Copy link
Contributor Author

trusktr commented Jun 28, 2016

The distributedCallback would need to be fired each time distribution changes. For example,

  • when adding a new shadow root to a host
  • when modifying the assigned slot of an element
  • when assigning a shadow tree's slot to a new slot in an even deeper shadow tree
  • when moving a slot to a new position within a shadow tree
  • when renaming a shadow tree's slots

Any other cases?

@trusktr
Copy link
Contributor Author

trusktr commented Jul 2, 2016

Another possibility could be to add a distributed or assigned event that we can listen to on the element, so in createdCallback:

customeElements.define('motor-scene',
  class extends HTMLElement {
    constructor() {
      this.addEventListener('assigned', e => this.doSomething())
    }

    doSomething() {}
  }
)

but, in that case we might as well move the connected and disconnected methods to the event space too:

customElements.define('motor-scene',
  class extends HTMLElement {
    createdCallback() {
      this.addEventListener('connected', e => this.doSomethingElse())
      this.addEventListener('disconnected', e => this.cleanUp())
      this.addEventListener('assigned', e => this.doSomething())
    }

    doSomething() {...}
    doSomethingElse() {...}
    cleanUp() {...}
  }
)

I think just allowing a method to be defined is the simplest way for class-definition time ergonomics:

class MotorScene extends HTMLElement {
  constructor() {...}
  connectedCallback() {...}
  disconnectedCallback() {...}
  assignedCallback() {...}
}
customElements.define('motor-scene', MotorScene)

But including the connected, disconnected (and possibly something like assigned) would make the API for retrieving an existing elements and adding behavior to them more clean, otherwise with the class-based methods alone we are limited to monkey patching in the scenarios when we want to modify already-instantiated elements. With events:

document.querySelector('#some-el').addEventListener('assigned', e => {...})

@trusktr
Copy link
Contributor Author

trusktr commented Jul 8, 2016

cc @domenic thoughts?

@trusktr
Copy link
Contributor Author

trusktr commented Jul 18, 2016

@rniwa

Speaking of events, it seems that a framework can asynchronously map the "flat tree" if the end user of the framework places custom elements from that framework at the root of the app and at a leaf positions, and finally triggering events, at which point Event.prototype.composedPath() will reveal one full branch of the flat tree?

I wanted to point out that distributedCallback would allow the same thing, for a framework to map the the flat tree (composed tree), but synchronously instead of asynchronously and with stricter requirements because it would require the framework to have a custom element placed into each shadow layer. So, the framework would be able to map the flat tree limited only to portions of the shadow layer hierarchy (if we were to visualize a tree of the shadow layers and child shadow layers) where the framework has custom elements from one layer to the next. As soon as one layer is missing the custom elements, then cooperating third party frameworks would be required to bridge that gap.

I think it's a good idea.

The reason I need this (or something similar) is to guarantee synchronous discovery (or lack of discovery, which is what I really want) of the flat tree between two shadow layers when my library's custom elements are used on both sides of the shadow boundary.

With strictly events, as it currently stands, this can only be achieved with event polling (we must poll because there's no way for an element to know it has been distributed because there's nothing like distributedCallback), which is very ugly.

Suppose we do write an event polling mechanism. Then, distributedCallback would allow us to change from a polling method to a one-shot method in distributedCallback, without having to re-write all the event-based code.

But, if something like distributedCallback existed to begin with, then the event code would not be needed, and only some synchronous code in distributedCallback along with synchronous code in a parent's slotchange handler (keeping in mind the above idea that slotchange should fire before distributedCallbacks of the elements distributed into the associated slot).

Quick question, which code can listen to slotchange events? Is there certain places where code that runs cannot listen to slotchange? If so, how does that work?

@trusktr
Copy link
Contributor Author

trusktr commented Jul 18, 2016

@rniwa Shoot, even if distributedCallback was async, that would be fine. I just need some singular event in time in order for my custom element to know that it has been distributed.

The main problem I'm trying to solve is so that my motor-node element can do something like

"Hey, I've been distributed. Okay, let me emit an event to see if some other motor-node is on the other side of the slot. If there is another motor-node on the other side, then I've successfully connected with my team mate on the other side, and I can go ahead and create a scene graph connection with that motor-node friend on the other side.

"But!

"If there's not an motor-node friend on the other side of the slot I've been distributed into, I will know this because I did not get a response back after I emitted my event message. In this case, I know that I can inform the developer of this situation with a helpful (possibly error) message in the console."

@trusktr
Copy link
Contributor Author

trusktr commented Jul 18, 2016

^ That last paragraph is the most important. That's my real problem, and I simply don't see a well-defined way of achieving this. @domenic @hayatoito @rniwa @treshugart

@hayatoito
Copy link
Contributor

My opinion did not change from: #504 (comment)

BTW, can we merge this issue to #504? It looks the similar discussion is happening on both sides.

@hayatoito
Copy link
Contributor

Let me close this in favor of #504.

@trusktr trusktr changed the title [idea] distributedCallback for Custom Elements [idea] slottedCallback for Custom Elements Aug 31, 2016
@trusktr
Copy link
Contributor Author

trusktr commented Aug 31, 2016

To align more with v1 terminology, I renamed from distributedCallback to slottedCallback.

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