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

时间:2018-10-28 23:32:42

标签: javascript web-component custom-element

我试图了解新的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对象?

4 个答案:

答案 0 :(得分: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属性设置值。

答案 1 :(得分:2)

  

但是找不到用户名,即this.querySelectorAll将返回null。

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

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

也可以使用.querySelector(...)代替.querySelectorAll(...)[0]来选择第一个匹配元素。

答案 2 :(得分: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);

答案 3 :(得分:1)

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

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

或与您类似的类似内容。 Google有一些文档here,大约描述了如何“使用JavaScript创建实例”的文档的约2/3。这可能非常强大,尤其是在像您的示例这样的for循环中