I am attempting to learn how to make a custom table with web components, and I'm trying to define properties on my custom elements with getters and setters. Here is a very simple table with one column, and a custom row element that has a line_number
property. Creating a custom table and then setting line_number
works as expected, with the setter being called and putting the value in the appropriate element. However, if line_number
is set by the custom table's constructor as in serial-log-setter-broken
below, the setter is not called and appears to be deleted (attempting to call it later does nothing).
customElements.define('serial-log-setter-broken', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).innerHTML = `
<table>
<thead>
<tr>
<th>Line Number</th>
</tr>
</thead>
<tbody></tbody>
</table>`
this.rows = []
for (let i = 0; i < 4; ++i) {
this.rows[i] = document.createElement('tr', {
is: 'serial-log-entry'
})
// .line_number's setter isn't called here, and seems to be deleted instead
this.rows[i].line_number = i + 1
this.shadowRoot.querySelector('tbody').appendChild(this.rows[i])
}
}
})
customElements.define('serial-log-setter-working', class extends HTMLElement {
constructor() {
super()
this.attachShadow({
mode: 'open'
}).innerHTML = `
<table>
<thead>
<tr>
<th>Line Number</th>
</tr>
</thead>
<tbody></tbody>
</table>`
this.rows = []
for (let i = 0; i < 4; ++i) {
this.rows[i] = document.createElement('tr', {
is: 'serial-log-entry'
})
this.shadowRoot.querySelector('tbody').appendChild(this.rows[i])
}
}
})
customElements.define('serial-log-entry', class extends HTMLTableRowElement {
constructor() {
super()
this.innerHTML = '<td>1</td>'
this._line_number_cell = this.querySelector('td')
}
get line_number() {
return Number(this._line_number_cell.textContent)
}
set line_number(v) {
console.log('Setter called')
this._line_number_cell.textContent = Number(v)
}
}, {
extends: 'tr'
})
console.log('This will print nothing')
document.getElementsByTagName('serial-log-setter-broken')[0].rows.forEach((v, i) => {
v.line_number = i + 1
})
console.log('This will print "Setter called" 4 times')
document.getElementsByTagName('serial-log-setter-working')[0].rows.forEach((v, i) => {
v.line_number = i + 1
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Setter overwritten</title>
</head>
<body>
<serial-log-setter-broken></serial-log-setter-broken>
<serial-log-setter-working></serial-log-setter-working>
</body>
</html>
Why does the setter work when used from outside the web component, but gets deleted when used from inside it?
Note: serial-log-setter-broken
and serial-log-setter-working
are nearly identical, and differ in whether the line_number
setter is used.
1条答案
按热度按时间klsxnrf11#
It about the order of definition.
The
serial-log-setter-broken
is defined and executed before theserial-log-entry
is defined. You can check this by adding a log message into the constructor of theserial-log-setter-broken
element. The log message is printed before the "This will print nothing" message.It is a strange behavior. I wasn't aware of it myself.
When you move the definition of the
serial-log-entry
above theserial-log-setter-broken
definition then it works.Note, you may reconsider extending built-in elements as Safari (the new IE) refuses to support the "is" attribute. See here and here .