Let's create an Element!
Tags: javascript, functional, beginners
Published: 2021-01-14 19:26:45

## 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:

```html
Hello <my-world lang="russian"></my-world>
```

First let's get the translation logic ready:

```javascript

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

```
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!

```javascript
const spanEl = document.createElement('span');
``` 

Put it in our document's body:

```javascript
document.querySelector('body').appendChild(spanEl);
```

Now that we have the span ready and placed inside <body>, let's populate it with our translated "World" for Spanish (not to worry! we will later extend the language selection 🙂). 

```javascript
spanEl.textContents = WORLD_DICT["spanish"];
```

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](https://github.com/tweinfeld/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.

```javascript
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.
);
```

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

```javascript
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"];
}
```
> _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:

```javascript
document.querySelector('body').appendChild(spanEl);
```

changes into:

```javascript
this.appendChild(spanEl);
```

`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](https://github.com/tweinfeld/element-f).

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.

```javascript
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));
  
}
```

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](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance)).

## 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:

```javascript
window.customElements.define('my-world', myWorldClass);
```

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

{% codepen https://codepen.io/tweinf/pen/OJRodpW %}

[Link to this Pen](https://codepen.io/tweinf/pen/OJRodpW?editors=0010)

## Find out more

  1. To fiddle around with ElementF, you can use its UMD version available through [unpkg](https://unpkg.com/element-f@2.0.0/dist/umd.min.js).
  2. [ElementF repo](https://github.com/tweinfeld/element-f).

Happy coding 😉