html Why are setters overwritten instead of being called when used within web components?

o75abkj4  于 2023-04-27  发布在  其他
关注(0)|答案(1)|浏览(95)

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.

klsxnrf1

klsxnrf11#

It about the order of definition.
The serial-log-setter-broken is defined and executed before the serial-log-entry is defined. You can check this by adding a log message into the constructor of the serial-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 the serial-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 .

相关问题