创建自定义HTML元素的新实例

问题描述 投票:5回答:4

我试图了解新的HTML自定义元素。

我的目标是,给定一些数据,创建自定义元素的n个实例。例如,给定10个用户的列表,创建10个用户html对象。

好的 - 所以我在html中定义了一个自定义元素

HTML

<template-user>
   <div class="user-name"></div>
</template-user>

然后我创建我的控制器

JS

class UserTemplate extends HTMLElement {
   constructor(){
      super();
      this.username = this.querySelectorAll('[class="user-name"]')[0];
   }
   setName(name){
      this.username.innerHtml = name;
   }
}
customElements.define('template-user', UserTemplate);

页面加载正常,但现在我对如何重用该元素感到困惑。如果我正在做正常的旧学校的东西,我会有一个for循环泵出一些HTML字符串并设置innerHTML的东西。但现在我宁愿做类似的事情

for(let i = 0; i < users.length; i++){
   let userTemplate = new UserTemplate();
   userTemplate.setName(user.name);
   // append to user list, etc..
}

当我尝试这样做时,它似乎几乎可以工作。但它找不到username,即this.querySelectorAll将返回null。这只是在我尝试构造这个元素的新实例时。那怎么样,我应该创建新的自定义元素DOM对象?

javascript web-component custom-element
4个回答
3
投票

确保您了解Web组件的构造函数的要求和限制:


https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance

4.13.2自定义元素构造函数的要求

在创作自定义元素构造函数时,作者受以下一致性要求的约束:

  • 对super()的无参数调用必须是构造函数体中的第一个语句,以便在运行任何其他代码之前建立正确的原型链和此值。
  • return语句不能出现在构造函数体内的任何位置,除非它是一个简单的早期返回(返回或返回此)。
  • 构造函数不得使用document.write()或document.open()方法。
  • 不得检查元素的属性和子元素,因为在非升级的情况下不会出现任何属性和子元素,并且依赖于升级会使元素变得不可用。
  • 元素不得获得任何属性或子元素,因为这违反了使用createElement或createElementNS方法的消费者的期望。
  • 通常,应尽可能将工作推迟到connectedCallback - 尤其是涉及获取资源或呈现的工作。但是,请注意,connectedCallback可以被多次调用,因此任何真正一次性的初始化工作都需要一个防护来防止它运行两次。
  • 通常,构造函数应该用于设置初始状态和默认值,以及设置事件侦听器和可能的影子根。

在元素创建期间,直接或间接检查其中一些要求,如果不遵循它们,将导致无法通过解析器或DOM API实例化的自定义元素。即使工作在构造函数启动的微任务中完成,也是如此,因为微任务检查点可以在构造之后立即发生。


您可以进行类似于此的更改:

class TemplateUser extends HTMLElement {
  static get observedAttributes() {
    return ['user-name'];
  }

  constructor(){
    super();
    this.attachShadow({mode:'open'});
    this.shadowRoot.innerHTML = '<div></div>';
  }
   
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      this.shadowRoot.firstChild.innerHTML = newVal;
    }
  }
 
  get userName() {
    return this.getAttribute('user-name');
  }
  
  set userName(name) {
    this.setAttribute('user-name', name);
  }
}

customElements.define('template-user', TemplateUser);


setTimeout( function () {
  var el = document.querySelector('[user-name="Mummy"]');
  el.userName = "Creature from the Black Lagoon";
}, 2000);
<template-user user-name="Frank N Stein"></template-user>
<template-user user-name="Dracula"></template-user>
<template-user user-name="Mummy"></template-user>

这使用shadowDOM存储<div>,然后通过user-name属性或通过userName属性设置值。


2
投票

但它无法找到用户名,即this.querySelectorAll将返回null。

当你创建一个新实例时,新元素没有子元素,所以querySelectorAll将返回一个空的NodeList。如果查询DOM并选择已在标记中定义的template-user,那么username属性将引用您的div元素。

如果您希望动态生成的template-user元素默认具有<div class="user-name"></div>子元素,则应在构造函数中创建并附加元素。

另外,为了选择第一个匹配元素,您可以使用.querySelector(...)而不是.querySelectorAll(...)[0]


1
投票

当您使用new通过Javascript创建自定义元素时,您可以设置一些将作为constrcutor()方法中的参数传递的变量:

for (let user of users) {
   document.body.appendChild( new UserTemplate(user.name) )
}

您可以将其保存并保存在对象变量中,或者将其用作Shadow DOM中模板文字字符串中的变量。

class UserTemplate extends HTMLElement {
    constructor(username){
        super()
        //this.username = username
        this.attachShadow({ mode:'open' })
            .innerHTML = `<div class="user-name"> ${username} </div>` 
    }
}
customElements.define('template-user', UserTemplate);

1
投票

您可以在JS中创建组件并分配属性

let templateUser = document.createElement('template-user');
templateUser.userName= 'Your name here';
document.body.appendChild(templateUser);

或类似于根据您的需求的东西。 Google有一些文档here,大概是他们描述如何“在JavaScript中创建实例”的方式的2/3。这可能非常强大,特别是像你的例子中的for循环

© www.soinside.com 2019 - 2024. All rights reserved.