{
"$type": "site.standard.document",
"content": "These days when you want to make a website you just can't avoid the words \"VueJS\" or \"ReactJS\" and for very good reasons, these libraries make developing a website much easier thanks to their component-based architecture and how they handle data/properties and update the relevant parts of your site accordingly _it's like magic!!_ ✨.\r\n\r\nBut for times when I need a simple component or the element I want does not have dynamic data, I ask myself \"Do I really need React/Vue for this? 🤔\", well that is where web components come in.\r\n\r\nWeb components are features (not the elements themselves) that help you do a lot of things, one of which is to create a custom element that can be used just like `input`, `div` and the rest.\r\n\r\nLet's start!.\r\n\r\n\r\n### Step 1: Define our component\r\n\r\nOne way to do this is by creating a class that implements the `HTMLElement` interface and give it a tag name by using the `customElements.define` function.\r\n\r\nAccording to [MDN.](https://developer.mozilla.org/)\r\n>The HTMLElement interface represents any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.\r\n\r\n```js\r\n//component.js\r\n\r\nclass MyComponent extends HTMLElement {\r\n constructor(){\r\n super();\r\n console.log(\"My component works!\");\r\n }\r\n}\r\n\r\ncustomElements.define(\"my-component\", MyComponent); //Register the new element\r\n```\r\n\r\n_Notice that the component name is hyphenated, this is because we are not allowed to make a component called something like `coolcomponent`, the name needs to resemble `x-cool-component` or `cool-component`_\r\n\r\nNow when we include `component.js` in our HTML file we can use the component we've just created.\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <my-component></my-component>\r\n</body>\r\n```\r\n\r\nAnd if we check the console we will see the message `\"My component works!\"`, That means our component is working fine.\r\n\r\n### Step 2: Element lifecycle\r\n\r\nJust like in Vue there are lifecycle callbacks namely\r\n\r\n- `connectedCallback`: this is called just after our element has been rendered.\r\n\r\n- `disconnectedCallback`: this is called when our element is about to be removed.\r\n\r\n```js\r\n//component.js\r\n\r\nclass MyComponent extends HTMLElement {\r\n constructor(){\r\n super();\r\n console.log(\"My component works!\");\r\n }\r\n\r\n connectedCallback(){\r\n console.log(\"Mounted!\")\r\n }\r\n\r\n disconnectedCallback(){\r\n console.log(\"Unmounted!\")\r\n }\r\n}\r\n\r\ncustomElements.define(\"my-component\", MyComponent);\r\n```\r\n\r\nWe now add a button to index.html which removes our element so we can test all the lifecycle callbacks.\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <my-component id=\"mycomponent\"></my-component>\r\n <button onclick=\"document.getElementById('mycomponent').remove()\">Remove Component</button>\r\n</body>\r\n```\r\n\r\nNow when we press the button our component is removed and we see the message `\"Unmounted!\"` in the console.\r\n\r\n### Step 3: Let's make something \r\n\r\nNow that we have the basic knowledge on how to make a custom element let's use it!. A good example of this is a clock element.\r\n\r\n_Warning!!!!, **CODE BOMB INCOMING!!!!!**_ 💣💣💣\r\n\r\n\r\n```js\r\n//component.js\r\n\r\nclass ClockElement extends HTMLElement {\r\n constructor(){\r\n super();\r\n // Time update interval id\r\n this.intervalID = 0;\r\n }\r\n \r\n pad(str){\r\n if(str.length == 1){\r\n str = \"0\"+str\r\n }\r\n return str;\r\n }\r\n\r\n //Check if hour is pm or am\r\n pmOrAm(hour){\r\n return Number(hour) < 12 ? \"am\" : \"pm\";\r\n }\r\n\r\n getTimeString(){\r\n const date = new Date();\r\n const seconds = date.getSeconds().toString()\r\n const hours = date.getHours().toString()\r\n const minutes = date.getMinutes().toString()\r\n\r\n var hoursNumber = Number(hours)\r\n var regularHOurs = hoursNumber-12<=0? hoursNumber : hoursNumber-12;\r\n return this.pad(regularHOurs.toString())+\":\"+this.pad(minutes)+\":\"+this.pad(seconds)+\" \"+this.pmOrAm(hours)\r\n }\r\n\r\n disconnectedCallback(){\r\n //Clear the timer interval\r\n clearInterval(this.intervalID);\r\n console.log(\"Unmounted\")\r\n }\r\n\r\n connectedCallback(){\r\n //Start the timer\r\n this.intervalID = setInterval(()=>{\r\n this.innerHTML = this.getTimeString()\r\n },1000);\r\n console.log(\"Mounted\")\r\n }\r\n}\r\n\r\ncustomElements.define(\"x-clock\",ClockElement)\r\n```\r\n\r\nLet me explain what is going on here,\r\n\r\n- We have renamed the element to `ClockElement` and registered it as `x-clock`\r\n\r\n- There is now an interval id used to identify and eventually clear the interval declared in `connectedCallback`\r\n\r\n- The `pad` method is used to add a 0 to numbers that are single digit, this makes the time look like `00:09:16` when it would look like `0:9:16`\r\n\r\n- The `pmOrAm` method returns the appropriate suffix for the time based on the hour\r\n\r\n- The `getTimeString` method looks complicated but it actually is not, we just get the current hour, minute and second and convert it into a nice string showing the time in 12-hour format\r\n\r\n- In the `connectedCallback`, we start a timer that sets the innerHTML of our element to the current time every 1000ms (1 second)\r\n\r\n- In the `disconnectedCallback` we clear the timer.\r\n\r\n\r\nNow that we understand that code, let's add the element to our website.\r\n\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <x-clock></x-clock>\r\n</body>\r\n```\r\n\r\n\r\n\r\n### Step 4: Attributes\r\n\r\nOur clock looks good so far but it can be better, we will now make it display either 24-hour or 12-hour format based on an attribute of our choice. I personally like this syntax :\r\n>`<x-clock military></x-clock>`\r\n\r\nso we will aim at using the existence of the attribute as a boolean.\r\n\r\n\r\n\r\n\r\n```js\r\n getTimeString(military){\r\n const date = new Date();\r\n const seconds = date.getSeconds().toString()\r\n const hours = date.getHours().toString()\r\n const minutes = date.getMinutes().toString()\r\n\r\n if(typeof military == \"string\"){\r\n return this.pad(hours)+\":\"+this.pad(minutes)+\":\"+this.pad(seconds)\r\n } else {\r\n var hoursNumber = Number(hours)\r\n var regularHOurs = hoursNumber-12<=0? hoursNumber : hoursNumber-12;\r\n return this.pad(regularHOurs.toString())+\":\"+this.pad(minutes)+\":\"+this.pad(seconds)+\" \"+this.pmOrAm(hours)\r\n }\r\n }\r\n\r\n disconnectedCallback(){\r\n //Clear the timer interval\r\n clearInterval(this.intervalID);\r\n console.log(\"Unmounted\")\r\n }\r\n\r\n connectedCallback(){\r\n const military = this.getAttribute(\"military\")\r\n this.innerHTML = this.getTimeString(military)\r\n this.intervalID = setInterval(()=>{\r\n this.innerHTML = this.getTimeString(military)\r\n },1000);\r\n console.log(\"Mounted\")\r\n }\r\n```\r\n\r\n\r\n\r\n_If you pay attention to the new code added in `getTimeString` you will see a very strange statement `typeof military == \"string\"`, this is there because when we set the attribute like this_: \r\n```html\r\n<x-clock military></x-clock>\r\n```\r\n_we get the value of the attribute as `\"\"` which in javascript is equivalent to false, so `if(military)` will return false even if the attribute exists_\r\n\r\nNow we can now choose to display either in 12-hour or 24-hour format by adding an attribute !!\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <x-clock></x-clock>\r\n <x-clock military></x-clock>\r\n</body>\r\n```\r\n\r\n### Step 5: Reactive state\r\n\r\nOur element currently does not change state in runtime even if our attribute has, that looks like it can be improved upon. So we will now make the element reactive to attribute changes.\r\n\r\nTo do this we use a `MutationObserver`, this helps us watch for any changes to our element.\r\n\r\nA good place to put this is in the element constructor. The `MutationObserver` constructor returns a MutationObserver that invokes a specified callback whenever there are changes to our element.\r\n\r\n```js\r\n constructor(){\r\n super();\r\n // Time update interval id\r\n this.intervalID = 0;\r\n this.observer = new MutationObserver((mutations)=>{\r\n for(var mutation of mutations){\r\n if(mutation.type == \"attribute\"){\r\n // React to changes\r\n }\r\n }\r\n });\r\n\r\n this.observer.observe(this,{\r\n attributes: true // Only listen for attribute changes\r\n });\r\n }\r\n```\r\n\r\nWe assign the observer to `this.observer` instead of `const observer` because we need to clean up the listener in our `disconnectedCallback`.\r\n\r\n```js\r\n disconnectedCallback(){\r\n //Disconnect observer\r\n this.observer.disconnect();\r\n\r\n //Clear the timer interval\r\n clearInterval(this.intervalID);\r\n\r\n console.log(\"Unmounted\")\r\n }\r\n```\r\n\r\nWhen the attribute changes we need to display the accurate time format, and for that, we also need to change `const military` to `this.military` so we can access the variable from the MutationObserver.\r\n\r\n```js\r\n constructor(){\r\n super();\r\n // Time update interval id\r\n this.intervalID = 0;\r\n this.observer = new MutationObserver((mutations)=>{\r\n for(var mutation of mutations){\r\n if(mutation.type == \"attribute\"){\r\n // React to changes\r\n this.military = this.getAttribute(\"military\");\r\n }\r\n }\r\n });\r\n\r\n this.observer.observe(this,{\r\n attributes: true // Only listen for attribute changes\r\n });\r\n }\r\n\r\n //Other code\r\n\r\n connectedCallback(){\r\n this.military = this.getAttribute(\"military\")\r\n this.innerHTML = this.getTimeString(this.military);\r\n this.intervalID = setInterval(()=>{\r\n this.innerHTML = this.getTimeString(this.military);\r\n },1000);\r\n console.log(\"Mounted\");\r\n }\r\n```\r\n\r\n\r\n\r\n### We are done 🎉🎉🎉🎉🎉🎉🎉🎉\r\n\r\nNot only have we just made our custom element but we made it react dynamically to changes. This only scratches the surface of what web components can do and I can't wait to see the great things you guys will use it for.\r\n\r\n_Once again this is not a replacement for VueJS (Or it's counterparts), it is only an alternative for when **Vue is overkill**_\r\n\r\n## Thanks For Reading!!",
"description": "How to make reactive/stateful components without a framework or library",
"path": "/posts/web-components-for-when-vuejs-is-too-much-8em",
"publishedAt": "2019-02-11T16:40:26.000Z",
"site": "https://blog.mainasara.dev",
"tags": [
"javascript",
"vuejs",
"webdevelopment",
"webcomponents"
],
"textContent": "These days when you want to make a website you just can't avoid the words \"VueJS\" or \"ReactJS\" and for very good reasons, these libraries make developing a website much easier thanks to their component-based architecture and how they handle data/properties and update the relevant parts of your site accordingly _it's like magic!!_ ✨.\r\n\r\nBut for times when I need a simple component or the element I want does not have dynamic data, I ask myself \"Do I really need React/Vue for this? 🤔\", well that is where web components come in.\r\n\r\nWeb components are features (not the elements themselves) that help you do a lot of things, one of which is to create a custom element that can be used just like `input`, `div` and the rest.\r\n\r\nLet's start!.\r\n\r\n\r\n### Step 1: Define our component\r\n\r\nOne way to do this is by creating a class that implements the `HTMLElement` interface and give it a tag name by using the `customElements.define` function.\r\n\r\nAccording to [MDN.](https://developer.mozilla.org/)\r\n>The HTMLElement interface represents any HTML element. Some elements directly implement this interface, while others implement it via an interface that inherits it.\r\n\r\n```js\r\n//component.js\r\n\r\nclass MyComponent extends HTMLElement {\r\n constructor(){\r\n super();\r\n console.log(\"My component works!\");\r\n }\r\n}\r\n\r\ncustomElements.define(\"my-component\", MyComponent); //Register the new element\r\n```\r\n\r\n_Notice that the component name is hyphenated, this is because we are not allowed to make a component called something like `coolcomponent`, the name needs to resemble `x-cool-component` or `cool-component`_\r\n\r\nNow when we include `component.js` in our HTML file we can use the component we've just created.\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <my-component></my-component>\r\n</body>\r\n```\r\n\r\nAnd if we check the console we will see the message `\"My component works!\"`, That means our component is working fine.\r\n\r\n### Step 2: Element lifecycle\r\n\r\nJust like in Vue there are lifecycle callbacks namely\r\n\r\n- `connectedCallback`: this is called just after our element has been rendered.\r\n\r\n- `disconnectedCallback`: this is called when our element is about to be removed.\r\n\r\n```js\r\n//component.js\r\n\r\nclass MyComponent extends HTMLElement {\r\n constructor(){\r\n super();\r\n console.log(\"My component works!\");\r\n }\r\n\r\n connectedCallback(){\r\n console.log(\"Mounted!\")\r\n }\r\n\r\n disconnectedCallback(){\r\n console.log(\"Unmounted!\")\r\n }\r\n}\r\n\r\ncustomElements.define(\"my-component\", MyComponent);\r\n```\r\n\r\nWe now add a button to index.html which removes our element so we can test all the lifecycle callbacks.\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <my-component id=\"mycomponent\"></my-component>\r\n <button onclick=\"document.getElementById('mycomponent').remove()\">Remove Component</button>\r\n</body>\r\n```\r\n\r\nNow when we press the button our component is removed and we see the message `\"Unmounted!\"` in the console.\r\n\r\n### Step 3: Let's make something \r\n\r\nNow that we have the basic knowledge on how to make a custom element let's use it!. A good example of this is a clock element.\r\n\r\n_Warning!!!!, **CODE BOMB INCOMING!!!!!**_ 💣💣💣\r\n\r\n\r\n```js\r\n//component.js\r\n\r\nclass ClockElement extends HTMLElement {\r\n constructor(){\r\n super();\r\n // Time update interval id\r\n this.intervalID = 0;\r\n }\r\n \r\n pad(str){\r\n if(str.length == 1){\r\n str = \"0\"+str\r\n }\r\n return str;\r\n }\r\n\r\n //Check if hour is pm or am\r\n pmOrAm(hour){\r\n return Number(hour) < 12 ? \"am\" : \"pm\";\r\n }\r\n\r\n getTimeString(){\r\n const date = new Date();\r\n const seconds = date.getSeconds().toString()\r\n const hours = date.getHours().toString()\r\n const minutes = date.getMinutes().toString()\r\n\r\n var hoursNumber = Number(hours)\r\n var regularHOurs = hoursNumber-12<=0? hoursNumber : hoursNumber-12;\r\n return this.pad(regularHOurs.toString())+\":\"+this.pad(minutes)+\":\"+this.pad(seconds)+\" \"+this.pmOrAm(hours)\r\n }\r\n\r\n disconnectedCallback(){\r\n //Clear the timer interval\r\n clearInterval(this.intervalID);\r\n console.log(\"Unmounted\")\r\n }\r\n\r\n connectedCallback(){\r\n //Start the timer\r\n this.intervalID = setInterval(()=>{\r\n this.innerHTML = this.getTimeString()\r\n },1000);\r\n console.log(\"Mounted\")\r\n }\r\n}\r\n\r\ncustomElements.define(\"x-clock\",ClockElement)\r\n```\r\n\r\nLet me explain what is going on here,\r\n\r\n- We have renamed the element to `ClockElement` and registered it as `x-clock`\r\n\r\n- There is now an interval id used to identify and eventually clear the interval declared in `connectedCallback`\r\n\r\n- The `pad` method is used to add a 0 to numbers that are single digit, this makes the time look like `00:09:16` when it would look like `0:9:16`\r\n\r\n- The `pmOrAm` method returns the appropriate suffix for the time based on the hour\r\n\r\n- The `getTimeString` method looks complicated but it actually is not, we just get the current hour, minute and second and convert it into a nice string showing the time in 12-hour format\r\n\r\n- In the `connectedCallback`, we start a timer that sets the innerHTML of our element to the current time every 1000ms (1 second)\r\n\r\n- In the `disconnectedCallback` we clear the timer.\r\n\r\n\r\nNow that we understand that code, let's add the element to our website.\r\n\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <x-clock></x-clock>\r\n</body>\r\n```\r\n\r\n\r\n\r\n### Step 4: Attributes\r\n\r\nOur clock looks good so far but it can be better, we will now make it display either 24-hour or 12-hour format based on an attribute of our choice. I personally like this syntax :\r\n>`<x-clock military></x-clock>`\r\n\r\nso we will aim at using the existence of the attribute as a boolean.\r\n\r\n\r\n\r\n\r\n```js\r\n getTimeString(military){\r\n const date = new Date();\r\n const seconds = date.getSeconds().toString()\r\n const hours = date.getHours().toString()\r\n const minutes = date.getMinutes().toString()\r\n\r\n if(typeof military == \"string\"){\r\n return this.pad(hours)+\":\"+this.pad(minutes)+\":\"+this.pad(seconds)\r\n } else {\r\n var hoursNumber = Number(hours)\r\n var regularHOurs = hoursNumber-12<=0? hoursNumber : hoursNumber-12;\r\n return this.pad(regularHOurs.toString())+\":\"+this.pad(minutes)+\":\"+this.pad(seconds)+\" \"+this.pmOrAm(hours)\r\n }\r\n }\r\n\r\n disconnectedCallback(){\r\n //Clear the timer interval\r\n clearInterval(this.intervalID);\r\n console.log(\"Unmounted\")\r\n }\r\n\r\n connectedCallback(){\r\n const military = this.getAttribute(\"military\")\r\n this.innerHTML = this.getTimeString(military)\r\n this.intervalID = setInterval(()=>{\r\n this.innerHTML = this.getTimeString(military)\r\n },1000);\r\n console.log(\"Mounted\")\r\n }\r\n```\r\n\r\n\r\n\r\n_If you pay attention to the new code added in `getTimeString` you will see a very strange statement `typeof military == \"string\"`, this is there because when we set the attribute like this_: \r\n```html\r\n<x-clock military></x-clock>\r\n```\r\n_we get the value of the attribute as `\"\"` which in javascript is equivalent to false, so `if(military)` will return false even if the attribute exists_\r\n\r\nNow we can now choose to display either in 12-hour or 24-hour format by adding an attribute !!\r\n\r\n```html\r\n<!-- index.html -->\r\n\r\n<body>\r\n <h1>Hello world!</h1>\r\n <x-clock></x-clock>\r\n <x-clock military></x-clock>\r\n</body>\r\n```\r\n\r\n### Step 5: Reactive state\r\n\r\nOur element currently does not change state in runtime even if our attribute has, that looks like it can be improved upon. So we will now make the element reactive to attribute changes.\r\n\r\nTo do this we use a `MutationObserver`, this helps us watch for any changes to our element.\r\n\r\nA good place to put this is in the element constructor. The `MutationObserver` constructor returns a MutationObserver that invokes a specified callback whenever there are changes to our element.\r\n\r\n```js\r\n constructor(){\r\n super();\r\n // Time update interval id\r\n this.intervalID = 0;\r\n this.observer = new MutationObserver((mutations)=>{\r\n for(var mutation of mutations){\r\n if(mutation.type == \"attribute\"){\r\n // React to changes\r\n }\r\n }\r\n });\r\n\r\n this.observer.observe(this,{\r\n attributes: true // Only listen for attribute changes\r\n });\r\n }\r\n```\r\n\r\nWe assign the observer to `this.observer` instead of `const observer` because we need to clean up the listener in our `disconnectedCallback`.\r\n\r\n```js\r\n disconnectedCallback(){\r\n //Disconnect observer\r\n this.observer.disconnect();\r\n\r\n //Clear the timer interval\r\n clearInterval(this.intervalID);\r\n\r\n console.log(\"Unmounted\")\r\n }\r\n```\r\n\r\nWhen the attribute changes we need to display the accurate time format, and for that, we also need to change `const military` to `this.military` so we can access the variable from the MutationObserver.\r\n\r\n```js\r\n constructor(){\r\n super();\r\n // Time update interval id\r\n this.intervalID = 0;\r\n this.observer = new MutationObserver((mutations)=>{\r\n for(var mutation of mutations){\r\n if(mutation.type == \"attribute\"){\r\n // React to changes\r\n this.military = this.getAttribute(\"military\");\r\n }\r\n }\r\n });\r\n\r\n this.observer.observe(this,{\r\n attributes: true // Only listen for attribute changes\r\n });\r\n }\r\n\r\n //Other code\r\n\r\n connectedCallback(){\r\n this.military = this.getAttribute(\"military\")\r\n this.innerHTML = this.getTimeString(this.military);\r\n this.intervalID = setInterval(()=>{\r\n this.innerHTML = this.getTimeString(this.military);\r\n },1000);\r\n console.log(\"Mounted\");\r\n }\r\n```\r\n\r\n\r\n\r\n### We are done 🎉🎉🎉🎉🎉🎉🎉🎉\r\n\r\nNot only have we just made our custom element but we made it react dynamically to changes. This only scratches the surface of what web components can do and I can't wait to see the great things you guys will use it for.\r\n\r\n_Once again this is not a replacement for VueJS (Or it's counterparts), it is only an alternative for when **Vue is overkill**_\r\n\r\n## Thanks For Reading!!",
"title": "Web Components, for when VueJS is too much"
}