Original link:
ayushgp.github.io/htm…


Translator: Ali Yun – Yishu

Web Components have been around for a while. Google has worked hard to make it more widely available, but it is still not well supported by most major browsers, with the exception of Opera and Chrome.

But through the polyfill, you can build your own from now on the Web Component, you can find related support here: www.webcomponents.org/polyfills

In this article, I’ll show you how to create HTML tags with styles, interactive features, and elegant organization in their own files.

introduce

Web Component is a set of Web platform apis that allow you to create new, customizable, reusable, and packaged HTML tags for use in regular Web pages and Web applications.

Custom components are built on the Web Component standard and can be used in modern browsers or with any JavaScript libraries and frameworks that interact with HTML.

Features to support Web Components are increasingly being added to the SPECIFICATION of HTML and DOM, making it easy for Web developers to extend HTML with new elements that encapsulate style and custom behavior.

It gives you the ability to create reusable components using only pure JS/HTML/CSS. If HTML doesn’t meet the requirements, we can create a Web Component that does.

For example, if your user data is associated with an ID, you want to have a component that can fill in the user ID and retrieve the corresponding data. HTML might look something like this:

<user-card user-id="1"></user-card>
Copy the code

This is a basic application of a Web Component. The following tutorial will focus on how to build this user card component.

The four core concepts of Web Component

The HTML and DOM standards define four new standards to help define Web Components. These criteria are as follows:

  1. Custom Elements: Web developers can use Custom Elements to create new HTML tags, enhance existing HTML tags, or redevelop components that other developers have already completed. This API is the cornerstone of Web Component.
  2. HTML Templates: HTML Templates define new elements that describe a way to use client Templates based on the DOM standard. Templates allow you to declare tag fragments that can be parsed into HTML. These fragments are not used at the start of the page load and are instantiated at run time.
  3. Shadow DOM: Shadow DOM is designed as a tool for building component-based applications. It solves some common problems of Web development, such as allowing you to isolate component DOM from scope, simplifying CSS, and so on.
  4. HTML Imports: HTML Templates allow you to create new Templates, and HTML Imports allow you to import these Templates from different files. A separate HTML file management component can help you better organize your code.

Defining custom elements

We first need to declare a class that defines how elements behave. This class needs to inherit from the HTMLElement class, but let’s skip that and talk about the life cycle methods of custom elements. You can use the following lifecycle callback functions:

  • connectedCallback– Emitted whenever an element is inserted into the DOM.
  • disconnectedCallback– Emitted whenever an element is removed from the DOM.
  • attributeChangedCallback– Triggered when attributes on an element are added, removed, updated, or replaced.

Create usercard.js under UserCard:

class UserCard extends HTMLElement { constructor() { super(); this.addEventListener('click', e => { this.toggleCard(); }); } toggleCard() { console.log("Element was clicked!" ); } } customElements.define('user-card', UserCard);Copy the code

In this example we have created a class that defines the behavior of custom elements. customElements.define(‘user-card’, UserCard); The function call tells DOM that we have created a new custom element called user-card, whose behavior is defined by the UserCard class. Now we can use the User-card element in our HTML.

We would use the https://jsonplaceholder.typicode.com/ API to create our user card. Here is a sample of the data:

{ id: 1, name: "Leanne Graham", username: "Bret", email: "[email protected]", address: { street: "Kulas Light", suite: "Apt. 556", city: "Gwenborough", zipcode: "92998-3874", geo: {lat: "-37.3159", LNG: "81.1496"}}, phone: "1-770-736-8031 x56442", website: "hildegard.org" }Copy the code

Create a template

Now let’s create a template that will be rendered on the screen. Create a new file named usercard.html with the following contents:

<template id="user-card-template">
  <div>
    <h2>
      <span></span> (
      <span></span>)
    </h2>
    <p>Website: <a></a></p>
    <div>
      <p></p>
    </div>
    <button class="card__details-btn">More Details</button>
  </div>
</template>
<script src="/UserCard/UserCard.js"></script>
Copy the code

Note: We prefixed the class name with card__. In earlier versions of browsers, we could not use shadow DOM to isolate the component DOM. This way we can avoid unexpected style overrides when styling components.

Writing style

Now that we’ve created the card template, let’s decorate it with CSS. Create a usercard.css file with the following contents:

.card__user-card-container {
  text-align: center;
  display: inline-block;
  border-radius: 5px;
  border: 1px solid grey;
  font-family: Helvetica;
  margin: 3px;
  width: 30%;
}

.card__user-card-container:hover {
  box-shadow: 3px 3px 3px;
}

.card__hidden-content {
  display: none;
}

.card__details-btn {
  background-color: #dedede;
  padding: 6px;
  margin-bottom: 8px;
}
Copy the code

Now, introduce this CSS file at the top of the usercard.html file:

<link rel="stylesheet" href="/UserCard/UserCard.css">
Copy the code

With the style in place, we can continue to refine the functionality of our component.

connectedCallback

Now we need to define what happens when we create the element and add it to the DOM. Notice the difference between the constructor and connectedCallback methods here.

The constructor method is called when the element is instantiated, while the connectedCallback method is called every time the element is inserted into the DOM. The connectedCallback method is useful when performing initialization code, such as fetching data or rendering.

Tip: At the top of usercard.js, define a constant currentDocument. It is necessary in imported HTML scripts to allow those scripts to have access to the DOM of the imported template. Define it like this:

const currentDocument = document.currentScript.ownerDocument;
Copy the code

Next define our connectedCallback method:

ConnectedCallback () {const shadowRoot = this.attachShadow({mode: 'open'}); // Select the template and clone it. Finally, the cloned node is added to the root node of shadowDOM. // The current document needs to be defined to get DOM permissions to import HTML. const template = currentDocument.querySelector('#user-card-template'); const instance = template.content.cloneNode(true); shadowRoot.appendChild(instance); // Select the user-id attribute from the element // Note that we specify the card like this: // <user-card user-id="1"></user-card> const userId = this.getAttribute('user-id'); // Get data according to user ID, And use the data returned to render the fetch (` https://jsonplaceholder.typicode.com/users/${username} `). Then ((response) = > response. The text ()) .then((responseText) => { this.render(JSON.parse(responseText)); }) .catch((error) => { console.error(error); }); }Copy the code

Rendering user data

We have defined the connectedCallback method and bound the cloned template to Shadow Root. Now we need to populate the template content and fire the Render method after the fetch method gets the data. Let’s write the Render and toggleCard methods.

Render (userData) {// Use the API to manipulate the DOM to fill the different areas of the card // all elements of the component exist in the shadow DOM, So we use this. ShadowRoot DOM / / this property to obtain the DOM can only in the sub tree species was found this. ShadowRoot. QuerySelector (' card__full -name '). The innerHTML = userData.name; this.shadowRoot.querySelector('.card__user-name').innerHTML = userData.username; this.shadowRoot.querySelector('.card__website').innerHTML = userData.website; this.shadowRoot.querySelector('.card__address').innerHTML = `<h4>Address</h4> ${userData.address.suite}, <br /> ${userData.address.street},<br /> ${userData.address.city},<br /> Zipcode: ${userData.address.zipcode}` } toggleCard() { let elem = this.shadowRoot.querySelector('.card__hidden-content'); let btn = this.shadowRoot.querySelector('.card__details-btn'); btn.innerHTML = elem.style.display == 'none' ? 'Less Details' : 'More Details'; elem.style.display = elem.style.display == 'none' ? 'block' : 'none'; }Copy the code

Now that the component is complete, we can use it in any project. To continue the tutorial, we need to create an index.html file and write the following code:

<html> <head> <title>Web Component</title> </head> <body> <user-card user-id="1"></user-card> <script SRC = "https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.14/webcomponents-hi.js" > < / script > < link rel = "import" href="./UserCard/UserCard.html"> </body> </html>Copy the code

Since not all browsers support Web Component, we need to introduce the webComponents.js file. Notice that we used HTML reference statements to introduce our component.

To run this code, you need to create a static file server. If you don’t know how to create one, you can use a simple static service like static-server or JSON-server. In this tutorial, we will install static-server:

$ npm install -g static-server
Copy the code

Then in your project directory, run the server with the following command:

$ static-server
Copy the code

Open your browser and go to localhost:3000, and you can see the component we just created.

Tips and Tricks

There’s a lot more about Web Components that hasn’t been covered in this short article, but I’d like to briefly present some tips and tricks for developing Web Components.

Component naming

  • The name of a custom element must contain a dash. so<my-tabs><my-amazing-website>Is a legal name, and<foo><foo_bar>I can’t.
  • When adding new tags to HTML, ensure forward compatibility and do not register the same tag repeatedly.
  • Custom element tags cannot be self-closing, because HTML only allows some elements to be self-closing. Write imaging<app-drawer></app-drawer>This is the closed label form.

Expand the component

You can use inheritance to create components. For example, if you want to create a UserCard for two different types of users, you can create a basic UserCard and expand it to two specific types of user cards. To learn more about component inheritance, check out the Google Web Developers’ Article.

Lifecycle Callbacks Lifecycle Callbacks

We create a connectedCallback method that fires automatically when the element is added to the DOM. We also have a disconnectedCallback method that fires when an element is removed from the DOM. AttributesChangedCallback (attribute, oldval, newval) method will be triggered when we change the attribute of custom components.

Component elements are instances of classes

Since component elements are instances of classes, you can define common methods in those classes. These common methods can be used to allow other custom components/scripts to interact with these components rather than just changing their properties.

Define private methods

Private methods can be defined in a number of ways. I prefer to use (execute functions immediately) because they are easy to write and understand. For example, if you’re creating a component that has very complex internal functionality, you could do something like this:

(function() {// Use the first self argument to define private functions // Pass the argument from the class when calling these functions function _privateFunc(self, otherArgs) {... } // Now functions are only available in your class's scope. Class MyComponent extends HTMLElement {... // Define the following function that gives you a way to interact with this element doSomething() {... _privateFunc(this, args) } ... } customElements.define('my-component', MyComponent); }) ()Copy the code

Freeze class

To prevent new attributes from being added, freeze your class. This prevents an existing property of the class from being removed or an enumerable, configurable, or writable property of an existing property from being changed, as well as preventing the stereotype from being modified. You can use the following methods:

class MyComponent extends HTMLElement { ... }
const FrozenMyComponent = Object.freeze(MyComponent);
customElements.define('my-component', FrozenMyComponent);
Copy the code

Note: Freezing classes prevents you from adding patches at run time and makes your code difficult to debug.

conclusion

This tutorial on Web Component is very limited. This can be partly blamed on React, which has a big impact on Web Components. I hope this article has given you enough information to try to build your own custom components without adding any dependencies. You can find more information about Web Components in the Custom Components API Spec.

You can read Part 2 of the tutorial here: Building Web Component – Part 2 with pure JS!