DEV Community

Cover image for Let's create an Element!
Tal Weinfeld
Tal Weinfeld

Posted on

Let's create an Element!

Let's create an Element

Let's create an Element. A basic one. One that can translate the word "World" into 3 languages: Spanish, French and Russia.

At the end of the day it will look like this:

Hello <my-world lang="russian"></my-world>
Enter fullscreen mode Exit fullscreen mode

First let's get the translation logic ready:


const WORLD_DICT = {
  "spanish": "mundo",
  "french": "monde",
  "russian": "mir"
};

Enter fullscreen mode Exit fullscreen mode

Using this simple object I can get the right translation for "World" by retrieving the value for a key named after the language I'm after.

  • WORLD_DICT["spanish"] will yield the value mundo
  • WORLD_DICT["french"] will return monde

Ok! Now let's display the translated word. To do that we'll need a hosting element. A <span> would fit perfectly!

const spanEl = document.createElement('span');
Enter fullscreen mode Exit fullscreen mode

Put it in our document's body:

document.querySelector('body').appendChild(spanEl);
Enter fullscreen mode Exit fullscreen mode

Now that we have the span ready and placed inside

, let's populate it with our translated "World" for Spanish (not to worry! we will later extend the language selection 🙂).
spanEl.textContents = WORLD_DICT["spanish"];
Enter fullscreen mode Exit fullscreen mode

If we'd combine all the code snippets above, we'd get a nice span with the word "mundo" inside it placed in our <body> tag.

Now let's move on to creating a real HTML element that can translate to all three languages!

Creating our custom element class

In order to create a custom HTML element, two steps are required:

  1. Create a Class that extends HTMLElement (don't worry if you're unfamiliar with these concepts). It is called a custom element class.
  2. Register that class using the window.customElement.define(); method.

For the first task, I will use Element-F, a library that helps simplify the creation of custom elements.

Let's begin by passing ElementF two arguments:

  1. A creation function. This function will be called whenever a new instance of our custom element <my-world> is used, and will decide the way it will look and behave.
  2. The names of the attributes (passed as strings in an array) that we'd like to offer to our element's users. ElementF will let us know whenever any of them changes. In our case, we'd only need the attribute name "lang", with which our users could set a language.
const myWorldClass = ElementF(
  creationFunction,   // This is the creation function (we'll deal with it next
  ["lang"]        // That's a list of attributes that we wish to be available to our element's users.
);
Enter fullscreen mode Exit fullscreen mode

Let's define our creationFunction. First, I'll just copy all our code from above inside it:

const creationFunction = function(){

  const WORLD_DICT = {
    "spanish": "mundo",
    "french": "monde",
    "russian": "mir"
  };

  const spanEl = document.createElement('span');
  document.querySelector('body').appendChild(spanEl);
  spanEl.textContents = WORLD_DICT["spanish"];
}
Enter fullscreen mode Exit fullscreen mode

When you're writing a creation function for ElementF, use only the long-form function definition (function(){}) instead of arrow functions (()=>...). We'll soon find out why.

This, believe it or not, is already a great leap towards our goal!

There are, of course, some changes in order.

First, we wish to host our <span> within our own element, not the <body> tag. So:

document.querySelector('body').appendChild(spanEl);
Enter fullscreen mode Exit fullscreen mode

changes into:

this.appendChild(spanEl);
Enter fullscreen mode Exit fullscreen mode

this inside our creation function references our actual element. So all I needed to do is append our span to it.

Because our creation function provides us with this, we cannot use the arrow-style function definition syntax (()=>{}). Using arrow function will cause this to be whatever its definition context is.

Second step would be to listen to changes to "lang" and respond by displaying the correct translation for it.

For this, we'd need to use something that ElementF passes to our creation function - a "life" event emitter.

Listening to life events using life is pretty straight forward:

life.on([eventName], [handler]);

ElementF provides a handful of events to respond to. You can find the complete list here.

Now, instead of just populating our <span> only once, and only with the translation to Spanish, let's keep updating it whenever "lang" attribute changes.

const creationFunction = function(life){

  const WORLD_DICT = {
    "spanish": "mundo",
    "french": "monde",
    "russian": "mir"
  };

  const spanEl = document.createElement('span');
  life.on('attribute:change', ({ newValue })=> spanEl.textContents = WORLD_DICT[newValue]);
  life.once('connect', ()=> this.appendChild(spanEl));

}
Enter fullscreen mode Exit fullscreen mode

Now we're listening to an event called attribute:change. Whenever it occurs, our handler function will be called, and as an argument it will be passed an object with newValue and previousValue fields. newValue is the freshly set value, which is all we need.

If you've been paying close attention, you probably noticed another change I've slipped in. I listen to an event called connect, which happens anytime my element is added to a document.

A clarification is in order 😇:

  1. I've listened to the connect event with once method. once is just like on, but will only invoke my handler once.
  2. I'm required to wait for the connect event before I can interact with the element's DOM (to append <span>) because of how custom elements are designed (see spec).

Let's use it!

At this stage we have a custom element class ready, but the DOM will not recognize it yet. This line will register it for us as my-world tag:

window.customElements.define('my-world', myWorldClass);
Enter fullscreen mode Exit fullscreen mode

That's it! Now let's try our snippet from above:

Link to this Pen

Find out more

  1. To fiddle around with ElementF, you can use its UMD version available through unpkg.
  2. ElementF repo.

Happy coding 😉

Top comments (0)