Re: [w3c/webcomponents] A new attribute similar to is="", but would allow multiple behaviors to be attached to a given element. (#662)

@prushforth 

> So are you proposing a) <web-map mixin="map mymapbehaviour"> or b) <map mixin="map mymapbehaviour"> where behaviour named "map" is the native behaviour that I was getting from inheriting from HTMLMapElement ?

Not quite. So the behaviors can be applied to absolute any element (unless there's a way to limit them, but I'll skip that idea for now).

So, for example, suppose we have behaviors `foo` and `bar` defined:

```js
class Foo { ... }
class Bar { ... }
elementBehaviors.define('foo', Foo)
elementBehaviors.define('bar', Bar)
```

They can be used on any element:

```html
<div has="foo bar"></div>
<div has="foo"></div>
<map has="bar"></map>
<any-element has="foo bar"></any-element>
```

It's merely a way to instantiate a specific class for any element, so that the instantiated class can react to the lifecycle of the target element.

In

```html
<div has="foo bar"></div>
```

There's at least three things happening:

1. An instance of HTMLDivElement is created, with whatever logic/properties it has. `this` inside of it's methods refers to the element itself.
2. An instance of `Foo` is created, with whatever logic/properties it has. `this` inside of it's methods refers to the the instance of `Foo`, with a reference to the `div` element passed into its constructor and/or other methods. The `this` in the methods of the `Foo` instance _do not_ refer to the element.
3. Similar for `bar` as in point 2, just replace Foo with Bar.

How you use these behaviors and for what purpose is up to you (you choose which behaviors to apply to which elements).

In your examples, `<web-map mixin="map mymapbehaviour">` and `<map mixin="map mymapbehaviour">`, both are valid. 

In the

```html
<web-map mixin="map mymapbehaviour">
```

example, if `web-map` is a Custom Element, then we have the following happening:

1. An instance of WebMap (or similarly named, which extends from HTMLElement for example) is created, with whatever logic/properties it has. `this` inside of it's methods refers to the element itself, just like in the `div` example. Only in this case the instance is a Custom Element, not a built-in element.
2. An instance of the class associated with the `"map"` behavior is created, with whatever logic/properties it has. Note that the `"map"` behavior is completely unrelated to the `<map>` element. `this` inside of it's methods refers to the the instance of the class associated with the `"map"` behavior, with a reference to the `web-map` element passed into its constructor and/or other methods. The `this` in the methods of the class associated with`"map"` _do not_ refer to the element.
3. Similar for `mymapbehaviour` as in point 2, just replace `"map"` with `"mymapbehaviour"`.

Finally, in my example, the `Foo` and `Bar` classes can specify methods similar to Custom Elements, and again, these are for any purpose that the authors of `Foo` and `Bar` might imagine:

```js
class Foo {
  constructor(el) {
    // A behavior is constructed when it's name is added to an element's has=""
    // attribute, or when the parser first encounters an element and creates it
    // and that element already had the name it its "has" attribute.

    console.log(el) // a reference to the element
    console.log(this) // a reference to this class instance
    console.log(el === this) // false
  }

  removedCallback(el) {
    // This is called when the behavior's name is removed from the element it
    // was instantiated for.
    //
    // For example, some other code might have called `el.setAttribute('has',
    // 'bar')` which no longer contains the name "foo", so removedCallback() is
    // called.
  }

  connectedCallback(el) {
    // do something with el anytime that el is added into the DOM.
  }

  disconnectedCallback(el) {
    // do something with el anytime that el is removed from the DOM.
  }

  attributeChangedCallback() {
    // do something anytime that one of el's attributes are modified.
  }
}

class Bar {
  constructor(el) { /* same description as with Foo */ }
  removedCallback(el) { /* same description as with Foo */ }
  connectedCallback(el) { /* same description as with Foo */ }
  disconnectedCallback(el) { /* same description as with Foo */ }
  attributeChangedCallback() { /* same description as with Foo */ }
}
```

Even more lastly, a new instance of a behavior is created for each element it is assigned to. If we have

```html
<div has="foo"></div>
<div has="foo"></div>
<div has="foo"></div>
```

then just like there are three instances of `HTMLDivElement`, there are also three instances of `Foo`, one `Foo` per `div`.

---

It could be possible to add an additional feature, where a singleton class can be specified, so that only one instance of it is instantiated for all elements it is assigned to. Suppose the API was like this:

```js
class Baz {
  constructor(el) { /* same description as with Foo */ }
  removedCallback(el) { /* same description as with Foo */ }
  connectedCallback(el) { /* same description as with Foo */ }
  disconnectedCallback(el) { /* same description as with Foo */ }
  attributeChangedCallback() { /* same description as with Foo */ }
}

// Let's use this behavior only inside a given ShadowDOM root (another feature).
shadowRoot.elementBehaviors.define('baz', Baz, {singleton: true})
```

and that we had this markup (inside the shadow root):

```html
<any-element has="baz"></any-element>
<other-element has="baz"></other-element>
<div has="baz"></div>
```

In this case there'd be only one instance of `Baz`, and anytime any of those elements are connected, disconnected, or attributes change, the methods of the single instance of `Baz` would be called, with the element passed in. If we, for example, modified an attribute on the `div` and `other-element`, then the single instance's `attributeChangedCallback` would be fired twice, and each time `el` would be a reference to a different element (the `div` first, then the `other-element` second).

The `Baz` `constructor` would only be called once, the first time the singleton behavior is used on any element, and `removedCallback` would only be called once, when the `baz` behavior is removed from the last element to have it.

---

There's probably more considerations to be ironed out, like for example, what if an element is removed from DOM and never added back. This would obviously call a behavior's `disconnectedCallback`. But would it also call the `removedCallback`? Perhaps both methods are called in that case, and it would be possible for `removedCallback` to be called by not `disconnectedCallback` when only the behavior is removed from the `has` attribute but the element wasn't disconnected.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/662#issuecomment-328002176

Received on Friday, 8 September 2017 05:25:31 UTC