【问题标题】:Create a Custom Element with different sub-types创建具有不同子类型的自定义元素
【发布时间】:2017-05-25 22:16:36
【问题描述】:

我目前正在使用自定义元素(Web 组件)实现数据表元素。表格可以包含用于呈现每一行的不同类型的单元格(文本、数字、日期等)。

例如

<my-table>
    <my-table-cell-text column="name"></my-table-cell-text>
    <my-table-cell-date column="dob" format="YYYY-MM-DD"></my-table-cell-date>
    <my-table-cell-number column="salary" decimals="2"></my-table-cell-number >
</my-table>

我还有一个 MyTableCell 类,所有单元格元素都扩展了它。这可以很好地共享通用功能,但是样式可能会很痛苦,因为每种单元格类型都是其自己的 html 标记。目前,我在扩展 MyTableCell 时添加了一个 css 类,但为了论证,可以说我不想这样做。

理想的解决方案是能够使用is 关键字扩展自定义元素,例如&lt;my-table-cell is="my-table-cell-text"&gt;but that's only allowed for built in html elements


我能想到 3 种方法来解决这个问题:

  1. 具有类似于&lt;input type=""&gt; 的语法,但这需要更多的工作,因为您不再扩展基类,而是创建相同元素的变体,这意味着您需要一种自定义方法来注册不同的变体,比如静态的MyTableCell.registerType

  2. 一种可组合的方法,我将渲染器元素 &lt;my-table-renderer-text&gt; 包装在通用 &lt;my-table-cell&gt; 中。这避免了自定义注册方法,但它更难编写并导致更多元素和更多样板代码,这反过来意味着性能损失。

  3. 两者兼而有之,用户写入&lt;my-table-cell type="text"&gt;,而单元格在内部使用document.createElement('my-table-rendener-'+ type) 之类的东西。这保留了选项 1 的更简单语法,同时仍然避免了自定义注册方法,但它具有与选项 2 相同的性能影响。


您能提出更好的替代方案吗?我错过了什么吗?

【问题讨论】:

  • 您想使用 v0 或 v1 规范设计自定义元素吗?
  • 我对两者都很满意。目前使用 v0,直到更多浏览器添加对 v1 的原生支持。据我所知,这两个规范都有相同的限制,即不能使用is 来扩展自定义元素

标签: html css dom web-component custom-element


【解决方案1】:

可以做的是使用&lt;td&gt;定制的内置元素

<table is="data-table>
   <tr>
       <td is="data-string">Bob</td>
       <td is="data-date">11/1/2017</td>
       <td is="data-number">44<td>
   </tr>
</table>

所有扩展共享同一个原型祖先。示例:

//common cell
class DataCell extends HTMLTableCellElement {...}

//typed cell
class StringCell extends DataCell {
    renderContent() { ... }
} 
customElements.define( 'data-string', StringCell, { extends: 'td' } )

这样,所有单元格都扩展了相同的&lt;td&gt; 元素,共享一个公共原型,但有自己的方法实现。

可以重写共享方法,共享方法可以调用派生原型对象的具体方法。

在此处查看运行示例:

//table
class DataTable extends HTMLTableElement {
    constructor() { 
        super()
        console.info( 'data-table created' )
    }
} 
customElements.define( 'data-table', DataTable, { extends: 'table' } );

//cell
class DataCell extends HTMLTableCellElement {
    connectedCallback() { 
        console.info( 'cell connected' )
        if ( typeof this.renderContent === 'function' ) 
            this.renderContent()
    }
} 

//cell string
class StringCell extends DataCell {
    renderContent()
    {
        console.info( 'data-string render' )
        this.innerHTML = '"' + this.textContent.trim() + '"'
    }
} 
customElements.define( 'data-string', StringCell, { extends: 'td' } )
table {
    border-collapse: collapse ;
}
td, th {
    border: 1px solid gray ;
    padding: 2px
}
<h4>Test Table Extension v1</h4>
<table is="data-table">
    <tr>
        <th>Id      <th>Name    <th>Age
    <tr>    
        <td>1       <td is="data-string">An      <td>20
    <tr>
        <td>2       <td is="data-string">Bob     <td>31

注意:如果你不想要类型扩展,你也可以使用自定义标签。这个想法是拥有一个通用原型和共享它的不同自定义元素(感谢标准原型继承)。

【讨论】:

  • 我不明白你在底部的注释。使用自定义标签是我现有的解决方案(我的常见原型是上面提到的 MyTableCell)。这适用于共享功能,但有我试图避免的样式问题,因为所有单元格类型都定义了自己的标签。
  • 我的节点是关于行为的。就样式而言,对于自定义标签,请使用单个元素 。它仍然与您的 #2-3 解决方案不同,因为您不需要在其中插入自定义元素。
【解决方案2】:

注意:这个答案与另一个答案是分开的,因为它本身非常广泛并且完全独立。

如果您使用带有(可选)type 属性的 自治自定义元素(即自定义标签):

<data-table>
    <data-row>    
        <data-cell>1</data-cell>       
        <data-cell type="string">An</data-cell>
        <data-cell type="number">20</data-cell>
    </data-row>
</data-table>

...你可以使用 MVC 模式:

  • 为通用单元View(和/或Model)定义一个类
  • 为专用视图(日期、数字、字符串)定义一个子类

genericstring 视图的示例:

class CellView {
    constructor ( view ) {
        this.view = view
    }
    render () {
        //default rendering
    }       
}

//String View
class CellStringView extends CellView {
    render () {
        console.info( 'special rendering', this.view )
        this.view.innerHTML = '"' + this.view.textContent + '"'
    }
}

在自定义元素定义中(可视为Controller):

  • 在创建时,实例化 View(或 Model)。
  • 当您想要渲染单元格(或处理数据)时,调用 View(或 Model)的(是否覆盖)方法。李>

自定义元素示例v1 类

class CellElement extends HTMLElement {
    constructor () {
        super()
        //create cell
        switch ( this.getAttribute( 'type' ) )
        {
            case 'string': 
                this.view = new CellStringView( this ) 
                break

            default:
                this.view = new CellView( this )
        }
    }
    connectedCallback () {
        //render cell
        this.view.render()
    }
} 

下面是现场直播的sn-p:

//View (MVC View)
class CellView {
  constructor(view) {
    this.view = view
  }
  render() {}
}

//String View
class CellStringView extends CellView {
  render() {
    console.info('special rendering', this.view)
    this.view.innerHTML = '"' + this.view.textContent + '"'
  }
}

//Element (MVC controller)
class CellElement extends HTMLElement {
  constructor() {
    super()
    //create cell
    switch (this.getAttribute('type')) {
      case 'string':
        this.view = new CellStringView(this)
        break

      default:
        this.view = new CellView(this)
    }
  }
  connectedCallback() {
    //render cell
    this.view.render()
  }
}
customElements.define('data-cell', CellElement)
data-table {
  display: table ;
  border-collapse: collapse ;
  border: 1px solid gray ;
}

data-row {
  display: table-row ;
}

data-cell {
  display: table-cell ;
  border: 1px solid #ccc ;
  padding: 2px ;
}
<h4>Custom Table v1</h4>
<data-table>
  <data-row>
    <data-cell>Id</data-cell>
    <data-cell>Name</data-cell>
    <data-cell>Age</data-cell>
  </data-row>
  <data-row>
    <data-cell>1</data-cell>
    <data-cell type="string">An</data-cell>
    <data-cell>20</data-cell>
  </data-row>
  <data-row>
    <data-cell>2</data-cell>
    <data-cell type="string">Bob</data-cell>
    <data-cell>31</data-cell>
  </data-row>
</data-table>

【讨论】:

  • 这与我的示例中的选项 1 基本相同,不同之处在于视图是在该 switch 语句中预定义的。与选项 1 一样,如果要允许用户定义自己的视图,则需要一种注册视图的方法,以便 CellElement 可以实例化它们。您之前的答案不必处理这个问题,因为视图本身注册为 td 的扩展
  • 感谢您花时间编写 2 个不同的、非常详细的答案。我开始这个问题,更多的是作为讨论,在似乎是自定义元素的用例中,架构师错过了它,或者他们认为它不值得支持。
  • 是的,这是您使用 MVC 实现的选项 1。我想他们决定不支持它,因为它可以通过标准原型(或类)继承(或保持简单)本地实现。
猜你喜欢
  • 1970-01-01
  • 2017-11-30
  • 2012-01-23
  • 1970-01-01
  • 2021-03-22
  • 1970-01-01
  • 2020-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多