Webcomponents: Custom event handlers support

Created on 13 Aug 2016  Â·  10Comments  Â·  Source: WICG/webcomponents

<img onerror>, <button onclick>, _&c._: these things are convenient and consistent. But custom events? One can create and dispatch custom events easily, but you don’t get the onfoo attribute and property for free, and it’s not so easy to get them working with the same semantics as the built-in event types.

I believe that Custom Elements is a great place to canonise the pattern and provide a standard implementation of this bit of scaffolding for custom events, so that they just work as expected, just like Custom Elements is all about making such things just work as expected from HTML authors. I expect that doing this will encourage the use of custom events as well (where people simply haven’t thought about it, for example, but they see that it’s now easy to do), which I think is probably a good thing.

Defining an event attribute foo has two parts; the property onfoo is added to the element’s prototype, and its HTML attribute onfoo is monitored and the onfoo property updated when the attribute changes.

I see two main approaches to specifying this (that an element of type MyFancyElement can have a foo event dispatched on it and thus should provide and support the onfoo attribute and property):

  1. A property like observedAttributes, producing a sequence of event names (['foo']). customElements.define would then add the properties to the class prototype at that point (this is feeling like metaclasses) and set whatever monitoring for the HTML attributes is required in place.
  2. Make an extra function call (customElements.defineEventAttributes(MyFancyElement, ['foo']) or similar) which adds the properties to the class prototype and sets things up, rather than making customElements.define do it.

(If we wait for standardisation of ES7 decorator proposal they could make the latter approach neater. Aside, I’d like customElements.define to support being called as a decorator.)

This proposal is easily polyfilled; https://github.com/chris-morgan/custom-element-events is such a polyfill (using the second approach, so it can sit on top of a native implementation or a polyfill).


There’s also the possibility of making this more general, using the second approach but making it work with _any_ element by putting the property on HTMLElement.prototype or similar. This would make custom events work as expected for _any_ element, and is not a bad idea. This would take it out of the scope of Custom Elements specification, though, and I’m not sure where it’d wind up, though still as a part of the web apps working group, I imagine. Maybe it’d be a new specification. I dunno. Anyway, I wouldn’t object to it being implemented generally like that. I believe it should also be straightforward to polyfill, I believe.

custom-elements

Most helpful comment

I think the correct approach for this, and other ways of matching native element conventions (like attribute reflection, or enumerated attributes, or token list attributes, or URL attributes, ...) is to see what developers in library space. E.g., if we see popular frameworks or libraries doing this, it makes sense to possibly bake such a helper in to the platform. But I don't think we should do so before we've given the ecosystem a chance to explore the problem space independently of standardization efforts.

All 10 comments

I think the correct approach for this, and other ways of matching native element conventions (like attribute reflection, or enumerated attributes, or token list attributes, or URL attributes, ...) is to see what developers in library space. E.g., if we see popular frameworks or libraries doing this, it makes sense to possibly bake such a helper in to the platform. But I don't think we should do so before we've given the ecosystem a chance to explore the problem space independently of standardization efforts.

I would be nice for convenience, but It's possible to write this behavior once, in a base class, then extend from it or mix it in (f.e. with class-factory mixins).

f.e., I use a WebComponent base class mixin that contains custom features, then I use it like this:

class MyElement extends WebComponent(HTMLButtonElement) {
  // ...
}

where the WebComponent function is a class-factory style mixin in order to pass to it the underlying native HTML*Elment class that I want to ultimately inherit from.

I'm contemplating splitting my features out into different mixins, f.e.

class MyElement extends ShadowComponent(StyledComponent(CustomEventComponent(HTMLButtonElement))) {
  // ...
}

Where for example ShadowComponent would give a custom element default shadow root features, StyledComponent might provide some pattern for applying a style to the component, and CustomEventComponent could provide the features you're asking for here. The benefits of these mixins if you can mix and match what features you need rather than having all the features if you don't need them all.

@chris-morgan you could have a simple convention like:

class MyImage extends HTMLImageElement {

  static get observedAttributes() {
    return ['onerror', 'onload'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (/^on/.test(name)) {
      if (newValue) {
        this[name] = newValue;
        this.addEventListener(name.slice(2), this);
      } else {
        delete this[name];
        this.removeEventListener(name.slice(2), this);
      }
    } else {
      // every other attribute logic
    }
  }

  // any event previously registered through the instance
  // will pass through this listener
  handleEvent(e) {
    return this['on' + e.type](e);
  }

}

Since attributes are notified as soon as the element is upgraded, you'll have your events and, whenever such attribute will be removed, or changed, you'll have them reflected.

The good part of using the instance as listener is that you'll never end up with double listeners or never removed one (like unhanded bound events would be) so it's quite easy to circumvent the current limit when it comes to custom attributes, keeping the on prefix like the de-fact standard to define an event.

I agree that we should leave this up to library authors for now. I am working on a component templating library and I plan on supporting the onfoo convention at some point. Some others might do things differently, I know Polymer has a different approach. We should wait and see what patterns work and which don't, then proceed. EWM, basically.

Since attributes are notified as soon as the element is upgraded,

Not true, if attributes exist on the element before they are upgraded,
Chrome doesn't call your attributChangedCallback (I guess technically, the
attribute didn't change after the instance was made).

On Sep 16, 2016 4:09 AM, "Andrea Giammarchi" [email protected]
wrote:

@chris-morgan https://github.com/chris-morgan you could have a simple
convention like:

class MyImage extends HTMLElement {

static get observedAttributes() {
return ['onerror', 'onload'];
}

attributeChangedCallback(name, oldValue, newValue) {
if (/^on/.test(name)) {
if (newValue) {
this[name] = newValue;
this.addEventListener(name.slice(2), this);
} else {
delete this[name];
this.removeEventListener(name.slice(2), this);
}
} else {
// every other attribute logic
}
}

// any event previously registered through the instance
// will pass through this listener
handleEvent(e) {
return this'on' + e.type;
}

}

Since attributes are notified as soon as the element is upgraded, you'll
have your events and, whenever such attribute will be removed, or changed,
you'll have them reflected.

The good part of using the instance as listener is that you'll never end
up with double listeners or never removed one (like unhanded bound events
would be) so it's quite easy to circumvent the current limit when it comes
to custom attributes, keeping the on prefix like the de-fact standard to
define an event.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/w3c/webcomponents/issues/546#issuecomment-247059876,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AASKzlnIMXZbTc8RGvWWXWb26eBj5T6Oks5qqBgmgaJpZM4Jjlhf
.

Or did I misunderstand what you meant?

On Sep 17, 2016 4:44 PM, "/#!/JoePea" [email protected] wrote:

Since attributes are notified as soon as the element is upgraded,

Not true, if attributes exist on the element before they are upgraded,
Chrome doesn't call your attributChangedCallback (I guess technically, the
attribute didn't change after the instance was made).

On Sep 16, 2016 4:09 AM, "Andrea Giammarchi" [email protected]
wrote:

@chris-morgan https://github.com/chris-morgan you could have a simple
convention like:

class MyImage extends HTMLElement {

static get observedAttributes() {
return ['onerror', 'onload'];
}

attributeChangedCallback(name, oldValue, newValue) {
if (/^on/.test(name)) {
if (newValue) {
this[name] = newValue;
this.addEventListener(name.slice(2), this);
} else {
delete this[name];
this.removeEventListener(name.slice(2), this);
}
} else {
// every other attribute logic
}
}

// any event previously registered through the instance
// will pass through this listener
handleEvent(e) {
return this'on' + e.type;
}

}

Since attributes are notified as soon as the element is upgraded, you'll
have your events and, whenever such attribute will be removed, or changed,
you'll have them reflected.

The good part of using the instance as listener is that you'll never end
up with double listeners or never removed one (like unhanded bound events
would be) so it's quite easy to circumvent the current limit when it comes
to custom attributes, keeping the on prefix like the de-fact standard to
define an event.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/w3c/webcomponents/issues/546#issuecomment-247059876,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AASKzlnIMXZbTc8RGvWWXWb26eBj5T6Oks5qqBgmgaJpZM4Jjlhf
.

You keep talking about Chrome v0 semantics in a repository devoted to the v1 spec. It's very off-topic.

v0 is still relevant.

Chrome 54 isn't on stable yet. There are people still using v0 API (native
or polyfilled), so if my elements run in a v0 environment, then I want them
to work there too.

v1 stems from ideas in v0, so this is (even if indirectly) on topic.

On Sep 17, 2016 5:31 PM, "Domenic Denicola" [email protected]
wrote:

You keep talking about Chrome v0 semantics in a repository devoted to the
v1 spec. It's very off-topic.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/w3c/webcomponents/issues/546#issuecomment-247816318,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AASKzgZaoJPc8Fzn3F5H8TCxLUD6xOW_ks5qrIZJgaJpZM4Jjlhf
.

I just raised https://github.com/skatejs/skatejs/issues/1417 to implement this at the library level, so we can see how it goes there, at least.

This can be done today and will be easier in the future with decorators. We can reconsider once there's more experience in libraries.

Was this page helpful?
0 / 5 - 0 ratings