if i create two custom elements and create one of them inside the other one, the attributes are not working on the new childs of the first element.
class Bar extends HTMLElement {
constructor() {
super();
const val = this.getAttribute('val') || 'no value';
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<div class='bar'>
<span>${val}</span>
</div>
`;
shadow.appendChild(wrapper);
}
}
customElements.define('x-bar', Bar);
class Foo extends HTMLElement {
constructor() {
super();
const loop = this.getAttribute('loop') || 10;
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
for(let i=0; i<loop; i++){
const b = document.createElement('x-bar');
b.setAttribute('val', `value #${i}`);
wrapper.appendChild(b);
}
shadow.appendChild(wrapper);
}
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>
if i create two custom elements and create one of them inside the other one, the attributes are not working on the new childs of the first element.
class Bar extends HTMLElement {
constructor() {
super();
const val = this.getAttribute('val') || 'no value';
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<div class='bar'>
<span>${val}</span>
</div>
`;
shadow.appendChild(wrapper);
}
}
customElements.define('x-bar', Bar);
class Foo extends HTMLElement {
constructor() {
super();
const loop = this.getAttribute('loop') || 10;
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
for(let i=0; i<loop; i++){
const b = document.createElement('x-bar');
b.setAttribute('val', `value #${i}`);
wrapper.appendChild(b);
}
shadow.appendChild(wrapper);
}
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>
im expecting that my output would be
value #0
value #1
value #2
as i have set the attr like this b.setAttribute('val', value #${i});
but im getting 3x no value
Any inputs on why that is? and/or how to fix it, Thanks!
Share Improve this question asked May 24, 2018 at 11:52 kejakeja 1,3631 gold badge15 silver badges21 bronze badges 1- Possible duplicate of Cannot access attributes of a custom element from its constructor – Supersharp Commented May 24, 2018 at 21:58
2 Answers
Reset to default 6Most people are unaware of the rules of a constructor for Web Components:
Here are the official docs:
https://w3c.github.io/webponents/spec/custom/#custom-element-conformance
The summary is:
Your constructor code:
- must have a parameter-less call to super() as first statement in constructor.
- must not have a return statement anywhere in constructor.
- must not call document.write() or document.open().
- must not inspect element's attributes.
- must not change or add any attributes or children.
In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.
In general I agree with @T.J. Crowder, but I would make one minor modification to the Bar
object:
class Bar extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
static get observedAttributes() {
// Indicate that we want to be notified
// when the `val` attribute is changed
return ['val'];
}
connectedCallback() {
// Render the initial value
// when this element is placed into the DOM
render(this.getAttribute('val'));
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal != newVal) {
// If the value for the `val` attribute has changed
// then re-render this element
render(newVal);
}
}
render(val = 'no value') {
this.shadowRoot.innerHTML = `
<div class='bar'>
<span>${val}</span>
</div>
`;
}
}
customElements.define('x-bar', Bar);
This used the standard of attributeChangedCallback
and observedAttributes
. While overriding the setAttribute
functionality works it is not future proof. If the API for setAttribute
were to change, in the future, then you would need to remember to fix your ponent. And doing this with many ponents racks up a lot of developer debt.
class Bar extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
// Render the blank DOM
this.shadowRoot.innerHTML = '<div class="bar"><span>no value</span><div>';
this._span = this.shadowRoot.querySelector('span');
}
static get observedAttributes() {
// Indicate that we want to be notified
// when the `val` attribute is changed
return ['val'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal != newVal) {
// If the value for the `val` attribute has changed
// then insert the value into the `<span>`
this._span.textContent = newVal || 'no value';
// OR: this._span.innerHTML = newVal || 'no value';
// But make sure someone has not tried to hit you
// with a script attack.
}
}
get val() {
return this._span.textContent;
}
set val(newVal) {
if (newVal == null || newVal === false || newVal === '') {
this.removeAttribute('val');
}
else {
this.setAttribute('val', newVal);
}
}
}
customElements.define('x-bar', Bar);
This second method done not re-render the entire DOM it simple inserts the modified attribute value into the <span>
tag.
It also provides properties so you can set the value through the attribute as well as through JavaScript:
var el = document.querySelector('x-bar');
if (el) {
el.val = "A New String";
setTimeout(()=>el.val = '';,2000);
}
You're not setting the attribute until after the constructor has already been called; note the logging:
class Bar extends HTMLElement {
constructor() {
super();
const val = this.getAttribute('val') || 'no value';
console.log("In constructor, val = " + val);
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
wrapper.innerHTML = `
<div class='bar'>
<span>${val}</span>
</div>
`;
shadow.appendChild(wrapper);
}
}
customElements.define('x-bar', Bar);
class Foo extends HTMLElement {
constructor() {
super();
const loop = this.getAttribute('loop') || 10;
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
for(let i=0; i<loop; i++){
console.log("Constructing...");
const b = document.createElement('x-bar');
console.log("Setting attribute");
b.setAttribute('val', `value #${i}`);
wrapper.appendChild(b);
}
shadow.appendChild(wrapper);
}
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>
You'll need to move your rendering logic out of the constructor so you can take attributes set post-construction into account. Perhaps by overriding setAttribute
:
class Bar extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({mode: 'open'});
this.wrapper = document.createElement('div');
this.render(); // Though calling methods from the constructor isn't ideal
shadow.appendChild(this.wrapper);
}
setAttribute(name, value) {
super.setAttribute(name, value);
if (name === "val") {
this.render();
}
}
render() {
const val = this.getAttribute('val') || 'no value';
this.wrapper.innerHTML = `
<div class='bar'>
<span>${val}</span>
</div>
`;
}
}
customElements.define('x-bar', Bar);
class Foo extends HTMLElement {
constructor() {
super();
const loop = this.getAttribute('loop') || 10;
const shadow = this.attachShadow({mode: 'open'});
const wrapper = document.createElement('div');
for(let i=0; i<loop; i++){
console.log("Constructing...");
const b = document.createElement('x-bar');
console.log("Setting attribute");
b.setAttribute('val', `value #${i}`);
wrapper.appendChild(b);
}
shadow.appendChild(wrapper);
}
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>
Calling methods from the constructor isn't ideal, though, you may want to fiddle with that a bit
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744329592a4568823.html
评论列表(0条)