2020년 9월 21일
이 글은 다음 언어로만 작성되어 있습니다. English, Español, Français, Indonesia, Italiano, 日本語, Русский, Türkçe, Українська, Oʻzbek, 简体中文. 한국어 번역에 참여해주세요.

Custom elements

We can create custom HTML elements, described by our class, with its own methods and properties, events and so on.

Once a custom element is defined, we can use it on par with built-in HTML elements.

That’s great, as HTML dictionary is rich, but not infinite. There are no <easy-tabs>, <sliding-carousel>, <beautiful-upload>… Just think of any other tag we might need.

We can define them with a special class, and then use as if they were always a part of HTML.

There are two kinds of custom elements:

  1. Autonomous custom elements – “all-new” elements, extending the abstract HTMLElement class.
  2. Customized built-in elements – extending built-in elements, like a customized button, based on HTMLButtonElement etc.

First we’ll cover autonomous elements, and then move to customized built-in ones.

To create a custom element, we need to tell the browser several details about it: how to show it, what to do when the element is added or removed to page, etc.

That’s done by making a class with special methods. That’s easy, as there are only few methods, and all of them are optional.

Here’s a sketch with the full list:

class MyElement extends HTMLElement {
  constructor() {
    super();
    // element created
  }

  connectedCallback() {
    // browser calls this method when the element is added to the document
    // (can be called many times if an element is repeatedly added/removed)
  }

  disconnectedCallback() {
    // browser calls this method when the element is removed from the document
    // (can be called many times if an element is repeatedly added/removed)
  }

  static get observedAttributes() {
    return [/* array of attribute names to monitor for changes */];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    // called when one of attributes listed above is modified
  }

  adoptedCallback() {
    // called when the element is moved to a new document
    // (happens in document.adoptNode, very rarely used)
  }

  // there can be other element methods and properties
}

After that, we need to register the element:

// let the browser know that <my-element> is served by our new class
customElements.define("my-element", MyElement);

Now for any HTML elements with tag <my-element>, an instance of MyElement is created, and the aforementioned methods are called. We also can document.createElement('my-element') in JavaScript.

Custom element name must contain a hyphen -

Custom element name must have a hyphen -, e.g. my-element and super-button are valid names, but myelement is not.

That’s to ensure that there are no name conflicts between built-in and custom HTML elements.

Example: “time-formatted”

For example, there already exists <time> element in HTML, for date/time. But it doesn’t do any formatting by itself.

Let’s create <time-formatted> element that displays the time in a nice, language-aware format:

<script>
class TimeFormatted extends HTMLElement { // (1)

  connectedCallback() {
    let date = new Date(this.getAttribute('datetime') || Date.now());

    this.innerHTML = new Intl.DateTimeFormat("default", {
      year: this.getAttribute('year') || undefined,
      month: this.getAttribute('month') || undefined,
      day: this.getAttribute('day') || undefined,
      hour: this.getAttribute('hour') || undefined,
      minute: this.getAttribute('minute') || undefined,
      second: this.getAttribute('second') || undefined,
      timeZoneName: this.getAttribute('time-zone-name') || undefined,
    }).format(date);
  }

}

customElements.define("time-formatted", TimeFormatted); // (2)
</script>

<!-- (3) -->
<time-formatted datetime="2019-12-01"
  year="numeric" month="long" day="numeric"
  hour="numeric" minute="numeric" second="numeric"
  time-zone-name="short"
></time-formatted>
  1. The class has only one method connectedCallback() – the browser calls it when <time-formatted> element is added to page (or when HTML parser detects it), and it uses the built-in