-
Notifications
You must be signed in to change notification settings - Fork 11
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
HTML Custom Elements: Ask Me Anything! #2
Comments
I am starting to share your enthusiasm for web components, but something that I get tripped up on is communication between components. Two more concrete questions:
|
@jeremiak: I think the best way to do it is with DOM events. You can dispatch CustomEvent instances from your element like so: this.dispatchEvent(new CustomEvent('change')); Custom events can bubble and be made cancelable just like Event instances, and you can attach arbitrary data via the var event = new CustomEvent('change', {
cancelable: true,
detail: {value: this.value}
});
this.dispatchEvent(event);
// if a listener calls event.preventDefault()...
if (event.defaultPrevented) {
// do something to roll back your change
} So your components can dispatch their own events or cause other elements (either their children or some other element in the document, e.g. if your component has an Event delegation is a good pattern to use with custom elements that have arbitrary content, too: you can listen for events that bubble at the component level and filter by selector ( |
So the primary negative I hear about web components is they generally don't work without JavaScript. Is this incorrect information? What would it take to make a web component work without JavaScript? Would it be comparable to React where you render your components server-side? |
That's definitely correct, @msecret. Custom elements should offer progressive enhancement. And I think that for most uses I've had so far, that makes perfect sense because pretty much any dynamic and/or interactive element on a web page is going to require JavaScript at some point anyway. For example, an accordion element could default to open without JavaScript: <usa-accordion expanded="false">
<h1><button aria-controls="content">Check out this cool content</button></h1>
<section id="content">
Cool content here!
</section>
</usa-accordion> But the custom element could then collapse the content unless it has xtag.register('usa-accordion', {
lifecycle: {
inserted: function() {
this.expanded = this.getAttribute('expanded') === 'true';
}
},
events: {
'click:delegate([aria-controls])': function(e) {
this.toggle();
}
},
accessors: {
expanded: {
attribute: 'expanded',
boolean: true,
set: function(expanded) {
var button = this.querySelector('[aria-controls]');
button.setAttribute('aria-expanded', expanded);
var id = button.getAttribute('aria-controls');
document.getElementById(id).setAttribute('aria-hidden', !expanded);
}
}
},
methods: {
toggle: function() {
this.expanded = !this.expanded;
}
}
}); |
@shawnbot, @msecret - This feels like XHTML days a bit; extending the HTML specification via XML. For styling purposes, I remember browsers being able to handle this pretty well. For assistive technologies, of course, I don't think it worked so well. And, it sounds like the "best-of-both-worlds" option can be found using the How fair is that assessment (trying to map my understanding)? |
@shawnbot - How do native HTML+user-agent components fall into this construct? Specifically with the idea of progressive enhancement. https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details - accordion capabilities, for example. |
How would I add markup when extending an element? So if I want to extend the |
@lukasoppermann First of all, I would treat the extra markup as purely progressive enhancement: if your component doesn't load (for whatever reason)—or even just until it does (when the network or your server is slow to load the JS, etc.)—then your input should appear and function normally. If your component needs to wrap itself in another element, though, it could do something like this (in ES2015, v1 API): class FancyInput extends HTMLInputElement {
connectedCallback() {
var container = document.createElement('div');
container.className = 'fancy-input-container';
this.parentNode.insertBefore(container, this);
container.appendChild(this);
}
}
customElements.define('fancy-input', FancyInput, {extends: 'input'}); If you're using the v0 API, then // "extends" should be a static getter
Object.defineProperty(FancyInput, 'extends', {
get() { return 'input'; }
});
document.registerElement('fancy-input', FancyInput); The one caveat here is that I'm not 100% sure whether the |
Sadly this results in a Additionally, would this work with shadow dom as well? No right, an input within the shadow dom will not be seen as normal. So I cannot add stuff into the shadow dom when extending the element? |
Okay, so I got it working by setting a |
@lukasoppermann Another way to handle that would be to check that its "new" parent is your expected container, a la: const CONTAINER_CLASS = 'fancy-input-container';
class FancyInput extends HTMLInputElement {
connectedCallback() {
if (!this.parentNode.classList.contains(CONTAINER_CLASS)) {
var container = document.createElement('div');
container.className = CONTAINER_CLASS;
this.parentNode.insertBefore(container, this);
container.appendChild(this);
}
}
} |
Also, sorry for not responding to your question, @joshbruce! My personal preference (and one that I've raised in uswds/uswds#1526) is to use the HTML standard elements whenever possible, and deploy polyfills for older browsers as needed. An accordion built entirely from const toggle = function(event) {
update(event.target);
};
const update = function(details) {
// set tabindex on the <summary>, etc.
};
// https://github.com/shawnbot/receptor
const receptor = require('receptor');
const keyup = receptor.delegate('details', receptor.keymap({
// these functions aren't defined, but you can imagine what they might do...
'ArrowUp': focusPreviousPanel,
'ArrowDown': focusNextPanel,
// etc.
}));
customElements.define('aria-accordion', class Accordion extends HTMLElement {
get multiselectable() {
return this.getAttribute('aria-multiselectable') === 'true';
}
set multiselectable(value) {
this.setAttribute('aria-multiselectable', value === true);
}
connectedCallback() {
this.addEventListener('toggle', toggle);
this.addEventListener('keyup', keyup);
// set ARIA attributes (for older browsers?)
Array.from(this.querySelectorAll('details')).forEach(details => {
details.setAttribute('role', 'tab');
});
Array.from(this.querySelectorAll('summary')).forEach(summary => {
summary.setAttribute('role', 'tabpanel');
});
}
disconnectedCallback() {
this.removeEventListener('toggle', toggle);
this.removeEventListener('keyup', keyup);
}
}); |
@shawnbot - This code fascinates me a bit...probably a bit too much, to be honest. :) I'm not a JS guru (I know enough to be dangerous); so, pardon ignorance (this is getting more into the voodoo layers for me). Objective-C, Swift, and PHP are my native languages (though I'm learning Angular via TypeScript by osmosis). The syntax you are using appears to be ECMA - not something like TypeScript. Curious if that is correct. Also, receptor seems very interesting. I believe you and I have talked delegate patterns before. I think we should do that again (I really would like to pick your brain some time)...but I'm not sure receptor is a "pure" (for lack of a better term) delegate. One of the advantages of delegation over broadcasting is a performance improvement - albeit minor in most practical applications (and many have worked around it). When a notification is broadcast, the system historically loops over all live objects and says, "Knock-knock, just checking, do you care about this? Nope, okay - go about your business. Yep, okay great - that thing you care about happened - bye." It's my understanding some platform developers have worked around this - but made it less obvious about what's going on - by sometimes creating a faux object instance that holds an array of all the listeners. Whenever an instance registers as an event listener, it's added to an array of instances listening for those notifications (granted this can carry its own baggage). What I characterize as a "pure" delegate pattern is more like the way Apple approaches delegations and protocols. Instance A can accept a delegate. Instance B registers as a delegate to instance A by implementing a protocol and notifying instance A of its desire to do the heavy lifting. Instance A responds to events by "passing the buck" to the delegate. The delegate can, if implemented this way, pass the buck again. And so on. It's possible that the delegate property could be an array of instances conforming to the protocol, which would all get called (serially or in parallel). Further, with regard to UI components specifically, the component may be developed to have default behavior if no delegate is present...a search bar updating the address bar, for example. Could have different behavior if a delegate is registered with the instance. And, of course that got way longer than intended. Always a pleasure, brother. Curious to hear your thoughts. /ht Tagging in a couple folks from the crew here: @diego-ruiz-rei and @jbabbs References: Swift protocols and delegates No PHP references, because, well, it's doesn't respond to things in the application sense. :) |
How does one define inline event handlers along the lines of I'm having trouble getting even onclick to come through but it would also be neat to define custom ones. |
@areinot As far as I know, there's no prohibition on inline event handlers in the custom elements v1 spec, so they should work. Your example works in Chrome's native implementation of the v0 spec, anyway. |
hi, I really liked your explanation, very well done! However, I was trying to define a Custom Element using a "functional" approach, following your example: // ES5
var CustomElement = {
prototype: Object.create(HTMLElement.prototype)
};
CustomElement.prototype.someMethod = function(arg) { /* ... */ };
// any accessors not passed to Object.create() can be defined like so.
// note that this is *exactly* what Object.create() is doing under the
// hood!
Object.defineProperties(CustomElement.prototype, {
someValue: {
get: function() { /* ... */ },
set: function(value) { /* ... */ }
}
}); I have tried to register it: customElements.define('x-test', CustomElement); but I get this error:
I fear that the class approach is the only possible in v1, but since ES2015 classes are mainly syntactic sugar for Javascript's very own prototypes I thought I could use a function approach in the same way... Do you have any information on this topic (defining v1 Custom Elements with functions rather than classes) ? thanks |
I've been using HTML custom elements for over a year now, and I kinda think they're the bees' knees. What would you like to know about them, and how can I help you put them to work?
The text was updated successfully, but these errors were encountered: