Web Components: How to Create Custom HTML Elements with Javascript | Christopher Dodd | Skillshare

Playback Speed


  • 0.5x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 2x

Web Components: How to Create Custom HTML Elements with Javascript

teacher avatar Christopher Dodd, Web Developer / Educator

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

13 Lessons (2h 30m)
    • 1. Introduction

      0:53
    • 2. Web Components - A Real Life Example

      1:36
    • 3. The 4 Web Component Specifications

      4:07
    • 4. Custom Elements Part 1

      17:17
    • 5. Custom Elements pt 2

      14:16
    • 6. The Shadow DOM

      15:20
    • 7. Slots and Templates

      12:10
    • 8. Customized Built-in Elements

      9:22
    • 9. ES Modules

      8:00
    • 10. Web Components in Practice Part 1

      29:20
    • 11. Web Components in Practice Part 2

      14:59
    • 12. Web Components in Practice Part 3

      22:36
    • 13. Conclusion

      0:30
  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

112

Students

--

Projects

About This Class

In today’s class, we’re gonna learn about the set of 4 specifications that make up web components meta-specification and how we can use these to create new custom and reusable HTML tags for use in web pages and web apps.

For those of you who are experienced coding with Javascript, learning how to make custom elements for your web apps and websites can be a useful tool for encapsulating functionality and reusing it anywhere within a particular project or across multiple projects.

Meet Your Teacher

Teacher Profile Image

Christopher Dodd

Web Developer / Educator

Top Teacher

Christopher Dodd is a self-taught web developer, YouTuber and blogger with a mission to help individuals learn the skills to freelance and make a living independently. 

Chris learned web development in 2015 in order to work remotely and travel the world and has done so for the past 2+ years. 

Through his YouTube channel, blog and Instagram, Chris inspires and educates newbie 'digital nomads' to chase their passions and pursue a location independent career.

See full profile

Class Ratings

Expectations Met?
    Exceeded!
  • 0%
  • Yes
  • 0%
  • Somewhat
  • 0%
  • Not really
  • 0%
Reviews Archive

In October 2018, we updated our review system to improve the way we collect feedback. Below are the reviews written before that update.

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. Introduction: Hello and welcome to this class on web components, your guide to creating custom HTML elements with JavaScript. I'm Christopher Dodd. I'm a freelance web developer and top teacher here on Skillshare.com, covering all things web development and online freelancing. In today's class, we're going to learn about the set of four specifications that make up the web components, meta specification, and how we can use these to create new custom and reusable HTML tags for use in webpages and web apps. For those of you who are experienced coding with JavaScript, learning how to make custom elements for your web apps and websites can be a useful tool for encapsulating functionality, and we're using it anywhere within a particular project or across multiple projects. If you're ready to learn how to create your own custom HTML elements with JavaScript, click onto the next video and I'll see you on the inside. 2. Web Components - A Real Life Example: Web Components was something that came up on my radar given my work with Shopify-powered e-commerce sites. It was June 2021 when Shopify revealed that their default theme had been updated to encapsulate the majority of the JavaScript logic into around 20 different web components, such as modals, quantity, inputs, and more. They even created their own slider component requiring no external JavaScript libraries. While I haven't been able to find any statements from Shopify as to why they chose to create their new online store 2.0 themes like this, I trust that if web components was how Shopify, a $multi-billion tech company wanted to structure the logic of their in-house developed themes, then this was a specification to take note on and learn more about. An e-commerce site such as those typically hosted on Shopify can be expected to have some common functionality that can be encapsulated via web components. To use Shopify's Dawn theme as an example, major points of interactivity such as the table of items in the cart page, the options for filtering products within a collection, and the product form, are all areas where functionality can be bundled into separate web components. At the end of this class, we're going to take Shopify's example as inspiration to build our own shopping cart functionality consisting of three separate components, all of which will interact with each other. But before we get there, we need to gain a deeper understanding of the theory behind web components. Starting with an overview of the four web component specifications. 3. The 4 Web Component Specifications: For this particular lesson, I'm going to refer to the website dedicated to helping developers share, discover and reuse web components, web components.org. So if you're ever lost at any point about the following theory, you can simply check back at web components.org/specs to follow along. As it's written here on web components.org, web components is a meta-specification made possible by four other specifications. The custom elements specification, the shadow DOM specification, the HTML template specification and the ES module specification. In this lesson, we'll briefly cover all of these specifications from a theory standpoint and in the next videos, we'll look at each specification in practice. Let's start with custom elements. The custom elements specification is the foundational specification that makes web components possible. Using this define method on the custom elements object, we can define a new custom element. The first argument is the name of the custom element, which is what we use to call it within the HTML. The second parameter is the constructor, which is where all the logic will go. We can create this constructed class by extending the HTML element class or an existing native HTML element class like in the example here, but we'll get to that a little later. Most of the time you'll be extending the regular HTML element class to create your components. This is called an anonymous custom element. Here we have access to the component as it exists on the DOM via the powerful, this keyword. Each time we use the custom element in our HTML, an instance is created and we can then write code within this class declaration relating specifically to the particular instance of the component. The next specification is the shadow DOM, which allows you to create a separate DOM to the regular DOM. The regular DOM is therefore sometimes referred to as the light DOM to distinguish it when talking about the alternative, which is the shadow DOM. The shadow DOM API allows you to attach a DOM subtree to elements within your web document. The attached shadow DOM is encapsulated, meaning that the style information inside it cannot apply to outside elements and vice versa. We can attach the shadow DOM to any element via the attached shadow method. But in the context of building web components, the shadow DOM is typically created directly on the web component itself using this.attachShadow. Using this shadow root object, we can build a subtree of elements, all of which are virtually invisible to the main light DOM. Next, let's talk about the HTML template specification. This is simply a way for us to create a HTML structure that will not render in place like normal HTML. We create a template via the template tag, place our HTML inside and when the page loads, the HTML is not rendered, but it's still accessible via JavaScript. As the name suggests, we can use these tags to create template code that can be cloned and used in multiple locations. In the context of building a web component, the template tag can be used to define the HTML of the component by cloning the HTML from within the tags and placing it either directly in the inner HTML of the component or into its shadow DOM. Finally, the ES module specification defines the inclusion and use of JS documents in other JS documents. While not essential for building web components, storing your web components as modular code that exists within its own JS file can be handy for project structuring, especially when bringing in third party web components. The syntax for the specification is to import your external script as usual, except this time you set module as the type parameter. Then you write input followed by the path to the file. The capabilities of the custom elements specification, the shadow DOM specification, the HTML template specification and the ES module specification work nicely together to help us write clean, encapsulated and reusable custom HTML elements. Theoretically, it is only the custom elements specification that is required to create custom elements, but as you'll see in later videos, combining capabilities from each specification is often the best way to get the most out of web components. 4. Custom Elements Part 1: Now that we've covered the four web component specifications in the last video, let's look at each of these in depth and in action inside our code editor. What I'm going to do is start a brand new project. I've got a folder here for my code. I'm going to create a new project folder and I'm going to call it web components. You can call it whatever you want. Then I'm going to drag this into my code editor, which is Visual Studio Code, and this will automatically open that folder for us. I'm then going to close this down, click new file over here, and create a HTML file. We're going to call it index.html. Now using Emmet within Visual Studio Code, I can output some boilerplate HTML by typing exclamation mark and then hitting "Enter". I'm going to change the title here just to web components. Then I'm going to create the JavaScript file, I'm going to call it component.js because we're going to be working with a single component first up. I'm going to do the classic hello world for our console. Head back over to here and then final modification I need to make to get this started is to actually link that JavaScript file. There we go. Now what I'm going to do is head back over to our folder and let's open that up in Google Chrome. It's Google Chrome for me, but you can use whatever browser you like. I'm going to use command option I to open up my developer tools here, and if I go to the console, I will just make this bigger for you guys, you can see hello world, which shows us that we are correctly linking that JavaScript file that we created over here. Awesome. Now that that's done, let's actually create our first web component. Again, there's some boilerplate to this. All we got to do, and this is going to be the same for every web component, apart from the built-in customized ones that we will look at later, I'm just going to call this web component and as you can see here, the first letter of each of the words in this class name here are capitalized. That's the convention we use. Then we type extends and then HTML elements. Then in here we need a constructor. Then we call the super function, which we're going to call every time we make a web component and basically what this does is it inherits the constructive functions of the class that it's extending. We need that in order to take advantage of what we're extending right here. Then we're going to go down here and finish this off by running the define method on custom elements. The first parameter is going to be what the element name is going to look like in our HTML document and then the second argument is the class name that we just created up here. There we have it, our first web component. Now in order to bring that onto our front end and run it in our HTML, we just now have this custom tag available to us, web component. That of course is equal to what we said over here in this define method. Maybe I'll run these side-by-side. Here we go. Finally, in order for us to verify that this web component is being constructed and run on the page, I'm going to put a console log in here and just type in hello world again. Let's go back to our HTML document, refresh the page and you can see the hello world is console logging. If we look on our page here, you can see that's because we have called that custom element. Now so far that doesn't seem to be any practical use of using this but as we go throughout this course, you'll start to see how web components encapsulate certain functionality to specific elements here. One of the ways we can see that is by using the, this keyword. If I just type in this in a HTML and console log that, then we are able to reference whatever we put in-between these tags. I will put the hello world there. Let's go back over here, refresh. You can see we've got some content in-between those web component tags and then that gets echoed here in our console as well. That just is going to show you that we can take the web component itself as reference using this and then do things within this component, which we'll start to see very shortly. That's our basic web component. Next up, let me show you how we can create inner HTML within our web component. I'm going to do this inside the constructor method because this runs whenever the component is constructed. I'll write some comments here. The first method is by simply setting the inner HTML. I'll write that down. We can go this inner HTML just like we did before and instead of outputting it, we can override it. I'll use the back ticks here so I don't have any issue with quotation marks, whether double quotes or single quotes. What I'm going to do is create a div and inside the div create a span and then in the span I'm going to say, this is a web component. Again, if I refresh over here and then I go and inspect this element, you can see that we have inserted this HTML within the web component. The second way we can do it is by creating elements and adding them to the DOM. I'll show you how we do that right now. Let's comment this up and I will show you the second option here. Let's just move this over so we can see more of it. Maybe increase the screen size. There we go. Then what I'm going to do is use document creates element and we can literally put in the element name here to create an element. But we need to store it somewhere, so I'm going to put a const div in front. That will store the newly created div element in this div constant. Then I'm going to create the span that we had before. I'm creating the exact same thing as up here. Document creates element span. Then now we've got our div and our span. Before I attach the div to our document, I'm going to update the inner HTML of the span to what we had before, this is a web component. Then what I can do is use these append child methods to basically construct the DOM. I can nest the span inside the div, and then I can then nest the div inside the web component itself, like this. Now if I go back over here, we've got a bit of an issue. That issue is that we put content in here. I'm just going to remove that, save that. If we head back, there you go, we will get div with the span inside. That's another way of doing it. The final way of doing it is to parse a HTML string. I will comment that out and then I will write the third comment, parse a HTML string. Let's just say for instance, in a particular situation I had a HTML string, and I wanted to put the HTML string as HTML inside of this web component. Let's just craft that again. Same thing again, div with a span inside, this is a web component, close the span, close the div. Then what I need to do because this is a string, I need to parse it. The way I can parse it is by using DOM parser. Let's look at that right now. I can set const inner HTML to new DOM parser, and then run the parse from string method, which will take the HTML string as its first argument. Then the format text/HTML. Then what that's going to do, is it's going to give me this parsed DOM. What I need to do is actually finds the body and then grab the inner HTML off of that. I'm basically creating a new DOM with this HTML string and then I'm grabbing the inner HTML of the body off of that new DOM. Then what I can simply do is say this inner HTML equals inner HTML. This being the web component, I'm setting the HTML of the web component to the inner HTML of this parsed HTML string. Everything seems good. Refresh over here and we get the exact same result. Now, it's not going to be immediately clear to you why you would need to use three different methods or which method to use. Use whichever method works for you, if this is the easiest and you can get away with it, definitely use that one. But there are certain situations in which maybe you're getting HTML returned from an API, which is the case in Shopify themes, then you need to use this DOM parser method, or maybe it's getting a little bit complicated with creating new elements that are fitting into a more complex HTML structure, in which case, you might want to use this method. In this simple example, it would seem like this is the easiest way to do it, which it is. But as the examples get more complex, you might start to need to use a combination of this or this. What I wanted to do in this little section was to show you how you can do that. The next thing I want to show you is custom attributes. I'm going to get rid of all of this. Don't worry, this will all be in the GitHub link, so you can look at this code whenever you want. What I want to show you now is that we can actually create our own custom attributes. Here in this web component tag, I could type in something like text or whatever parameter I wanted to put through. Let's just say this is a web component. I've got a parameter by the name of text and I'm parsing through that as a value. How do we use that? Well, I'm going to close down this. What I'm going to do is access that attribute via this.getAttribute. Then the argument for that is the attribute name itself, which is text. Let's just console log that to get started. Then we'll find a more practical use of it. Refreshing over here, if I go into the console, you can see this is a web component, and that matches the parameter I set through here. That's not very helpful so far. What I'm going to do is number 1, I'm going to set this into a variable. I'm going to create a variable within this component, so attaching it to this. I'm going to call it inner text content. But before I do that, I want to check whether this element actually has that attribute. I'm going to use the has attribute method parsing through text as the attribute name. Then I'm going to put that into the if block there. Again, this should work. There's no errors here, there's no console log, but that's because we haven't console logged anything or changed anything yet. What I might do here is just like before, use the method that we had before this simple method of just setting the inner HTML. What I'm going to do is use that same structure again with the div and the nested span. But in the nested span, I'm going to put a dynamic value based off of that attribute. Because we set this variable right here, what I'm going to do is put that in here this.innerTextContent. Then refresh over here. You can see we've got the same result as what we had before. Let's go back into our HTML. If I was to change this to, this is a component. That's what gets sent through to the span. Now the one issue with this is if we go back in here and let's say we have a pretty dynamic web component here where this value is going to change. If I change this to, this is a web component, again, just not a component.You'll see that nothing updates. That's because we actually have to watch for changes to attributes, for this to actually change. Back to the component.js file, we're going to talk more about life cycle methods in the next section. But for now, what I'll do is show you the life cycle method that we can use to check for attribute change. Here, our right attribute changed callback, and it takes three arguments. The first one is the attribute name itself. I'm going to say attr name. The second attribute is the old value, and the third attribute is the new value, the value that it's going to be changed to. Then I'm going to put it in a check here. If the atrr name is text, then let's update the inner HTML. Keep this structure. Instead of this inner text content, we're going to set that to the new value that comes through in the callback. That might seem like the last step, but there's one more step we need to do, which is to tell the web component to watch for this particular attribute. This is just a way for web components to be leaner and to make sure that they're not checking attributes that don't matter to the programmer. What we need to do for that is simply type static, get observed attributes. Then we're just going to return an array with all the attributes that we want to watch, which is just text. Let's head over to our browser here, refresh. We have a syntax error. That is because I need to put this outside the constructor. This is the constructor method right here. I need to put that outside, refresh over here. This should work. No errors. If I go into here and change, this is a component to this is a web component or anything else, you can see that the inner HTML has changed. We have successfully run this method here because we are checking for any observed attributes with the name of text. We check if the attribute name that's coming through this callback is text, and if so, we update the inner HTML to this new HTML structure with the new value. We've covered quite a lot already to do with the custom elements specification. I'm going to break up the second half of this video into a part 2. In the next part, we're going to look at lifecycle methods and custom events. 5. Custom Elements pt 2: In this Part 2 video, discussing the features of the custom elements specification, we're going to talk about life-cycle methods and custom elements. In the last video, we actually saw one of these life-cycle methods, the attribute change callback. I'm going to put this in as number 3, which we'll talk about in just a second. Bit of a disclaimer here, I'm going to consider constructor number 1 here even though technically it's not a life-cycle method, but it helps to look at constructor in the context of life-cycle methods anyway. There's a second one here, which I'll reveal very shortly, and a fourth one, which I'll reveal here. Let's talk about constructor first. The constructor, I'll write some some here and I'll put this in the GitHub for you guys so you can refer to it later. Let me fix this typo. This is run when the component is created in memory, but not necessarily attached to the DOM yet. According to my research, this is the best place for initialization, but not the best place for interacting with the DOM. Now, the difference between constructor and the next one is quite subtle. But if you ever have issues with putting something in this constructor function and it doesn't work, the next one is probably where you need to put it, and that one is the connected callback. I'll type that in here, connected callback. That is run when the element is actually attached to the DOM. It looks exactly like this, connected callback. This third one, as we saw before, attribute changed callback is simply checking if an attribute has changed. Pretty simple to understand. This fourth one that we're going to look at is the disconnected callback, which is the complete opposite of the connected callback. This runs when the element is detached from the DOM. You might think, well, why do we need that? It's useful for performing cleanup, so processes that we don't want to run anymore once we get rid of it. It looks exactly like the connected callback except it's disconnected callback. There is one or two more, but these are the main ones. We've seen constructor, we've seen attribute change callback. The two ones we haven't looked at yet until this video are connected and disconnected. What I'm going to do in here is put some console logs and then I can show you what it looks like when connected callback is run and disconnected callback is run. All I'm going to do is say connected callback, and then right write in here, disconnected callback. If I run this code now, if I go into my console, refresh the page, you can see connected callback is run. If I put in a console log here, and let's say constructor, and then we run over here, you can see constructor and connected callback is run. Here's where the subtle difference between constructor and connected callback happens. Connected callback is technically when the element is attached to the DOM and constructor is when it's created in memory. But the two are happening automatically once we put this component in our document. How can I demonstrate to you the difference between connected callback and constructor? Well, one of the ways I can show you this is by programmatically removing this web component from the DOM, still having in a memory and then reconnecting it. That's what I'm going to do in this example right here. Just above the component JS script tag, I'm going to create another script tag. What I'm going to do is grab a hold of the web component itself. I'm going to create a variable web component, and then we're going to use document query selector to grab the web component element. Just like we would any other element, we can do this with our own custom elements as well. Then what I'm going to do is just base this on timeout for the purposes of this example. I'll create this timeout function. Let's run it after one second. Then inside the timeout function, what I'm going to do is remove child on the document body and remove that web component HTML from the document body, only to reattach it again. Documentbody.append child web component. Basically, all this is doing is after one second of this page being loaded, we're going to remove that web component from the body which it's currently on and put it back into the body. Let's refresh over here and wait one second. You can see that the constructor function runs once because the web component is created once in memory. Then, of course, the connected callback happens pretty much simultaneously with constructor. But then what we see after one second, if I run this again, after one second, which is the parameter we put here for the timeout, you'll see that disconnected callback is run because we're removing a web component from the DOM and then we're putting it back so the connected callback runs again. Here you can see the subtle difference between constructor being created the memory and the interaction of the web component with the DOM. Often it happens at the same time. But if we were to take the web component and move it around our DOM, then connected callback and disconnected callback come in handy. We've demonstrated two in one there. But if we go back here and I actually go inside the elements section of my dev tools here, click on the web component. Actually, let's clear all of this. Click on this element now and delete it from our DOM. If I go over to the console tab, you can see that disconnected callback has run again. That's basically connected callback and disconnected callback. It's simply run when the element is attached to the DOM and when the element is detached from the DOM. We will see how we need to use connected callback in an example once we get to web components in practice later in this class, but for now, until you have an issue where you have to use connected callback, you can probably just use constructor for all your initializations and everything you want to run when this web component is created. Once you start to run into issues though with running code inside your constructor, that's when you need to start looking at the connected callback function. That's lifecycle methods. Finally, in this custom elements specification, I want to talk about custom events. What I'm going to do is clear out some of this code. I'm going to get rid of this set Timeout. I'm still going to keep my web component in this constant here. Hit, "Save" on that, head over here. What I'm going to do is update this HTML here. I'm going to have to do it twice because we've got this over here as well. I'm just going to create a button. In this button, I'm just going to put the content trigger special event. I'm going to copy that code over to here. When the attribute changes, which actually happens on the very first time the attribute gets set anyway. If we don't do this, if I leave this off and run the page, you'll see the button doesn't show up anyway because attribute change, callback is run as soon as it recognizes that attribute. This is not the cleanest code but we're going to copy and paste that over here. Now if I look at my component here and I click this button, we'll get it to do something very shortly. Let's get rid of these console logs. We don't need them anymore. We can keep this connected callback there. I'm going to get rid of these comments here, those I'll leave in the relevant branch of the GitHub so you can reference that later. I'll just get rid of those comments for now. Then what I'm going to do is create our own custom event. I'm going to create a method that's going to get run on our EventListener, so trigger special event. In order to create our own custom event, we can just assign new event. In the parameter here, we can call the event wherever we want. I'm just going to call it Special. Now that new event, that special event is stored in this special event constant. When this method is run, what we can do is we can dispatch that event by running dispatch event. Then in here running special event. Then what we're going to do up here is going to our connected callback and add an event listener to the click event on the button to run this special event. I'm going to find the button within this web component, which we can do via this query selector. Add EventListener, look for the click event. Then we can run this trigger special event. Then we're going to bind this, therefore, allowing us to reference this as the web component. If the bind method confuses you a little bit, don't worry. It confused me for awhile as well. All it does is make sure that this keyword inside the function is related to this, the web component, not this the function or anything else. It's just an extra step. If we don't use it we'll end up with this being not what we intended it to be. Now, what's going to happen is when we click on the button, we're going to trigger this special event which dispatches our own custom event, which is called special event. But if I click on the button, nothing really happens. Technically, this special event is triggered but we're not actually listening for that special event. Nothing really updates on the page. Let's change that. Let's go into our HTML file here. Underneath here in our script tag, I'm going to add that EventListener on our web component. We've already stored our web component here in this constant, so I can do web component, add EventListener. We can literally listen for the special event that we created, so we can put in special. Then let's just put in this anonymous function which simply will console log. The special event has been triggered. We've got three steps here. We have created a special event method which dispatches a custom event that we called special. We have attached that to the click EventListener on the button. Then we're going to listen for that special event on the web component itself and console log, the special event has been triggered if that event has been dispatched. If we go back to our document here and I click, "Trigger special event," you can see it'll run, the special event has been triggered. If I click that, you can see the number goes up here. Obviously, this simple example doesn't actually communicate the need for a custom event. But when you start to have more complex elements may be you want to have a button that triggers the special event. You want to have something else that triggers the special event. It just makes sense semantically to have a special event on your web component, then this comes in handy. An example that you might find in the real-world is Modals. You may have a closed event on a modal web component. That closed event might be triggered from a button click or clicking outside the modal or clicking Cancel. You've got three different separate events that you want to trigger the same event on the web component itself. Then you can check outside of your web component whether the closed event on that modal web component has been run. That's an example I've seen in the real-world for using custom elements. But again, I've made it really simple in this video, as simple as possible for you guys to see it in action. Again, if you don't feel the need to use this feature, you don't have to use it. But if you see it out in the real-world and you're trying to understand what is going on when you see something like this, well, this is the explanation. We can create custom events that we can dispatch on our web component. That allows us to look for events happening on the web component itself. It's part of this whole mindset of encapsulating functions and events within one component. That's all I wanted to talk about on the topic of the custom elements specification. In the next video, we're going to talk about the Shadow DOM. 6. The Shadow DOM: Welcome back, guys. In this video, we're going to pick up where we left off with the last video in terms of our little project here. But we're going to move into something called the Shadow DOM specification, which as we learned about in the theory section, is a way that we can attach a DOM sub-tree to a Web Component that is separate from the main DOM. So we've got our main DOM here as represented by our HTML. We can create what's called a Shadow DOM on our Web Component or on any element for that matter. And that DOM is separate to what is referred to sometimes as the Light Dom, which is shadow and light are opposites. So calling the Regular DOM the Light Dom is a way to differentiate it from the Shadow Dom. You might hear me refer to the Normal DOM as the Light Dom in this video. So just a note there. So what I'm going to do is remove some of this code for the custom events. It's a bit of silly code anyway, that was just to demonstrate a point. So I'm going to get rid of that connected callback and that special event. We don't really need, I'll leave the disconnected Callback in there. And I'll go over here and just remove all of this additional JavaScript from the HTML document. So let's jump straight in and attach a Shadow Dom to our custom Web Component. How we do this is very simple. What we do is run the method, attach shadow. And we do this on the this keyword which refers to the Web Component, of course. And all we need to do is pass in a JavaScript object. And we're going to pass in the mode of "open". We can also pass in the mode of "closed", but it's rather irrelevant. You can look up why we would pass in open or closed by doing some research, but it's not particularly important right now. So what I'm going to do is instead of having our HTML inserted directly on the element, what I'm going to do is put in here shadow Root. Now, this is a special keyword that allows us to attach this inner HTML to the shadow Root, which is the start of the DOM sub-tree within our basically Shadow DOM. Let me save that which already is. Switch over to our HTML. I've got a diagram here as well. By the way, they opened up between lessons. I'll show you that in just a second. And then let's refresh over here. Then let's go into elements, and let's click into that element. Now you can see that we've got the same thing as we had before, but we've also got a shadow Root. So then we can click into here and have a look, and you can see that we've got this inner HTML. Now the reason why we've got this other div there as well, I believe is because we haven't changed this to shadow Root. So I will change that, refresh over here. And now you can see if we go into our Web Component, we don't have that div and span inside, but what we do have is a Shadow Dom. And inside that Shadow Dom, we have a div and the span, and of course the button which doesn't do anything anymore inside that Shadow Dom. You might be thinking what's the difference between attaching it to the Shadow Dom versus attaching this HTML directly to the Web Component. And I'm glad you're wondering that because that's exactly what I'm going to talk about now in this following table. So basically, the main thing with Shadow Dom is CSS. Given that the Shadow Dom is separate to the main DOM, it doesn't inherit the CSS of the Light Dom, as we can see here in this table. If I go to the Shadow Dom content, the Light Dom CSS does not apply. On the flip side, any CSS within the Shadow Dom does not apply to the Web Component itself or any content outside. We can however, get past this by using the colon host pseudo-selector, which we'll talk about in just a second. So basically, this table summarizes the relationship between Light DOM CSS and Shadow DOM CSS in regards to the Web Component itself, slotted content within that Web Component, and the Shadow DOM content. I'm going to go through each of these examples very shortly, but let's just start with a basic example to start us off here. This is kind of skipping ahead into the next video, but I'm going to create a slot. So let me just go into component here. And instead of button, what I'm going to do is create a slot. I'm just going to copy it down here as well. Of course, any content that I slot in here, we'll go into where our slot tag has been set. So what I'm going to do is create a span and say this is a slotted-in element. And then what I'm going to do is start to write some CSS in our Light Dom. So I'm going to target the span and I'm going to give it a color of red. Hit "Save" on that refresh over here. And you can see this is a slotted-in element and the color is in fact red. But what you may have also noticed is that inside of this div is another span. And that span did not take on the CSS in our Light Dom and that's because it's part of the shadow Root. So if we wanted to style this particular tag, what we would do is go into the component here, and anywhere where we create our HTML, we create a separate style tag. And then we can color our span like such. Let's make it green. And again, not the cleanest code, but we have to copy this down here as well. I'll hit "Save" on that and then refresh over here. You can see that the Shadow Dom span is green and the slotted-in span is red. So this is the basics of styling the Shadow Dom. If you have an element within the Shadow Dom, the styling typically needs to happen inside the Shadow Dom as well. And that won't affect any span that is slotted in. Conversely, any elements within the Shadow Dom do not inherit the styles of the Light Dom. So as you saw here, when I put a style rule for color red on spans within the Light Dom, that did not apply to the span within the Shadow Dom. So that is the basic example of styling the Shadow Dom. But let's actually get back to our diagram here and let's go through all the different scenarios. So the first scenario using the Light Dom CSS to style the Web Component itself. What I mean by that is just like we are using the element name to target this span. We can also do that with a Web Component. So it works just like any other HTML element. We can just write Web Component and let's just say text-decoration, underline, save and refresh on that and you can see that the entire Web Component has an underline now. Going back over here to the Shadow Dom CSS, we can actually style the Web Component itself from within the Shadow Dom by using a special pseudo-selector colon host. So let's go back and do that now. So If I go into our component here, and before we do that span of green, let's do host. Maybe let's just make the font weight bold of everything in the web component. I'll hit "Save" on that, refresh over here. That's not working because of this attribute change callback. Let's actually clean this up now and make this clean code. Instead of setting the exact same code twice, what I'll do is I'll target the specific span in here and update that value to the new value. Instead of setting the inner HTML explicitly on the shadow root, I'll go back here, find that particular span on the shadow root, then target the inner HTML of that span, and then set that to the new value. That's much cleaner. That way we maintain the inner HTML structure of our shadow root without having to update the whole thing every time. That should make this work now, which it does, we now have the entire web component bold, and we were able to do that within the shadow root via using this host pseudo-selector. Let's go back to the table here. Let's look at the slotted content. The slotted content in the context of the Light DOM is not considered part of the Shadow DOM therefore, Light DOM styles apply here. We already saw that. We saw it in the case of this span here that's been slotted in. The color of that span is red. Again, as it's not considered part of the Shadow DOM, the Light DOM styles apply here. But as we saw before with the host selector, there is a way that we can style this slotted content within the Shadow DOM CSS as well. We just need to use the colon-colon slotted pseudo-class and we need to pass in a parameter. Let's go and do that right now. Let's go into our component.js file. Let's put in this slotted pseudo-class and the parameter. We're going to put in the span so that we're styling the slotted in span. Let me change the color to, let's say, gray. Then if we go over here and refresh, what do you think's going to happen? Let's find out. As you can see, there is no change to these colors. Why is that? If we have a look at this second element here, which is the slotted in element, and we look at the applied styles you can see that we've got our color red here, and then we've got our color gray for the span but the color red is overwriting. This is consistent with what I've written in the table here which is, in the case of conflicts, the styles from the Light DOM take precedence. You might think, I've put this last so the gray would take over but, in the case that there is a style for that particular slotted in content in the Light DOM, the style in the Light DOM gets to control the color. If we wanted to style this slotted in span inside our Shadow DOM, we need to make sure that this is not there. If I comment that out and then I refresh over here, you can see that we can style that to gray when it's not overwritten by a style that relates to the same element within our Light DOM. Refreshing over here, let's keep it to red, and let's head back to our final row apart from the exception here for Shadow DOM content in our table. Any Light DOM CSS will have no effect on our Shadow DOM. I think we can already see some examples of this. We can see that there is a color rule for red on all span elements. But if we go in here, we've got a span in our Shadow DOM and, of course, it's still green. We've seen that already and then with the Shadow DOM CSS, obviously, any CSS that we create in the Shadow DOM is going to have a direct effect on the Shadow DOM. Again, we saw that with styling our span element within the Shadow DOM, you can see that it is green. That's basically the whole table. The only exception is Light DOM CSS variables are accessible inside the Shadow DOM CSS. If you've used CSS variables before, basically how this works is, in our Light DOM, we can create some CSS variables. What I'm going to do is, on root, create a CSS variable called BG color and we'll set that to black. Then what I'll do is I'll go in to here and let's set the background color to the variable of BG color, hit "Save" on that, refresh over here. You can see that even though the CSS variable was defined in the Light DOM, we can still bring it into the Shadow DOM. It's just that the CSS rule needs to be in the Shadow DOM CSS. This gives us some control of the Shadow DOM content in terms of using variables within our Light DOM. If we did have a color scheme, I can change this to, let's make this yellow and hit "Save" on this. Now, you can see we can update values within the Light DOM and that will affect the Shadow DOM when using variables. But other than that, if we were to go in here and say span, background, color of, what should we do? Let's go blue. We've got this span rule twice so I'm going to just comment that one out. I'll hit "Save", refresh over here. You can see the blue background only applies to the slotted content. It does not apply to the span within the Shadow DOM. We can't actually change this color via the Light DOM unless, of course, we reference these variables. If you're ever lost, I will link you guys to this table right here. I'll put a link on your screen right now so you can go check it out when I publish this class. But essentially the Shadow DOM is mainly about CSS. It is a way for us to fix the CSS of an element perhaps if we were sharing elements across different projects and we wanted the element to look exactly the same, no matter which project it was in, the Shadow DOM would come in handy. But within your own private applications and web pages, I'm not sure the Shadow DOM is too required, which is why we're not going to use it in the practical example at the end of this class. But it is popular within web components so it was important to learn this lesson on the Shadow DOM. As always, this is all going to be committed to GitHub, so you can expect this code later. I'll, of course, share this table with you but that pretty much sums up the Shadow DOM. In the next video, let's talk about slots and templates. 7. Slots and Templates: Welcome back guys. In this video we're going to talk about slots and templates, which are pretty basic. We already used a slot in the previous video. We slotted it in some content here. Basically we nested a span inside of this web component and then we were able to choose where we wanted it to slide into this particular HTML structure within our shadow DOM. Having a look at that in the actual browser, you can see we've got our shadow root here. If I go into the div and go into this slotted content here, you can see that there's a link to the slotted in content which is right here. It's a weird thing where it's technically not part of the shadow DOM, but it is slotted into the shadow DOM here via this slot tag. Now we've already seen that in practice, the next thing to look at is name the slots, so if we want to use multiple slots within a particular web component. In order to demonstrate this, I'm going to remove all the code we don't really need for this demonstration. Again, we can leave in the disconnected callback, but I'm going to get rid of this attribute here. Get rid of that. Then I'm going to get rid of the attribute as it exists here in the HTML. I'm going to create another span here. Then I'm going to write this span has also been slotted in. If I hit, "Save" on that, let's see what happens now. If I refresh the page you can see there are two spans inside this one slot. But what we can do via name slots is fit these two spans into different parts of the same HTML structure. What do I mean by that? Well, let's go over to our component.js file here and let's change this. Let's just say we have two divs. The first div is going to have a **** for the first span and let's create another div that separate to that div and inside is going to be the second span that I slot into my web component. Once we name these slots, what's going to happen is this span is going to go into the first slot and this span is going to go into the second slot. We can start either in the HTML document or in the JavaScript document. I'm going to start here in the JavaScript, all I've got to do is name the slot by adding the name parameter here and then here I'm just going to call it Slot 1. Then I'm going to go down to the second slot and name it Slot 2. Then over in our index.html to link these up is I'm going to use the slot parameter, and I'm going to again use that name, so Slot 1, and then slot equals Slot 2. Now if I hit, "Save" and refresh over here, what do you think it's going to happen? Let's refresh and let's have a look inside. As you can see, we've got our two slotted elements over here, but in terms of where they fit into our shadow root structure, let's open this bad boy up and we can see that we've got two divs. In the first div, we've got the first slotted content, and in the second div, we've got the second slotted content. Now they're in separate divs and we might need that for whatever we need to do with our HTML structure, so styling and whatnot. That is how we can slot in multiple things into different parts of our shadow DOM structure. Following on from this, the next thing I want to show you are templates. I'm going to remove this code right here [NOISE] then I'm going to put in an HTML, the style rules and I'm going to head back to index.html here. I'm going to head in here and remove this content because what we're going to do is put content inside a template and then bring that content into the web component. We can do this one of two ways. I'm sure we can do it multiple ways, but there's two ways I'll show you in this video. We could put the template tag outside of the web component. In this case, let's give it an ID. I'm just going to call it template for simplicity sake. Then here, let's create our template. I'll just make a h1 and say this template code makes up the web component. Then I'm going to create a paragraph tag and using Emmet within Visual Studio Code, I can type Lorem hit, "Tab" and it'll generate Lorem Ipsum automatically. I'm just going to fix up the spacing here so you guys can see the entire paragraph. Hit, "Save" on that. Now, because this is a template tag, it will not get rendered on runtime. If I head back over here, run the code, you'll see that we have our template code here. We have this document fragment here, so we do have the code in our HTML, but nothing is rendering and that's because it's inside a template tag, which means it's not going to render unless we take that template code and render it using JavaScript. That's exactly what I'm about to do. I'm going to head back to our code editor here and head over to component.js. What I'm going to do is after we set our inner HTML here, which is just the style tag, what I'm going to do is find that template code. How I can do that is by document query selector. Basically I just want to find that element. It was a template with the ID of template. That's why I've put hash in front of it. Then I'm going to find the contents of that and then I'm going to run clone Node with the parameter of true. With the parameter of true that represents a deep clone, so any sub-elements within the template will be rendered as well. I can't really think of a situation in which you would want to run false here. Let's just keep it with true. Of course we're going to need to use that somewhat. I'm going to store it in a variable, a constant first. Now, we've got our const template. It looks like I've accidentally place this outside the constructor function, so I'm going to put that back in there. Sorry about that. Now, with this template, what I can do is I can attach that cloned node to the shadow root. I'm going to grab the shadow root here and run append a child, placing that template code in there as the argument. Let's refresh the page over here and see what happens. You can see we now have the template code running within our web component. If I go into the code of the web component here, you can see we have our shadow root and inside the shadow root we have the style tag that we set by explicitly setting the inner HTML. Then we have the h1 and p tag that we set in the template. What we can also do to make this a little bit more simpler and encapsulated is put the template tag inside the web component itself. I'm just going to copy that and then comment it out. Let's go inside the web component. Now, because we're inside the web component, we can just make reference to the template element rather than having an ID. I'm going to hit, "Save" on that and then head over here to our code and what we can do instead is take away this hash. Instead of document, we can say this. This will look up any template within our web component. I will hit, "Save" on that, refresh over here, and you see we get the exact same result. You can see the template is in the web component and then the code that we had in that template has been cloned within the shadow DOM. This works the same as if we weren't using the shadow DOM at all. Let's just remove the shadow DOM completely. Actually, I'm just going to comment it out because I think we might use it in the next video. Let's just append it to the actual inner HTML. Refresh over here and you'll see it's not appending and that's because we still have shadow root here. This append child refresh over here. Again, not working. What have we done? Cannot read properties of null. I believe the issue here is that we've set in our HTML explicitly, which is wiping out the HTML content inside the web component that we've put here, which is just this template tag. In this case, what I would do is I would head over here and let's just get rid of this and instead put this directly in the template, which is a bit cleaner anyway. Let's "Save" on that and then next to the template, I will put the style tag directly in our HTML. I hit "Save" on that refresh over here and the issue is gone now. Depending on how you want to do it, this actually looks pretty clean because we're not putting any HTML inside the JavaScript at all. We're putting all the HTML and the styles that go within into our web component. But because we're not using a shadow DOM anymore, these styles will theoretically apply to everything. If we go in here and put a span, say this is a span. Refresh over here. The styles from our light DOM that we have up here and our styles here inside the web component will both apply. If I have a look at this, let's have a look at this particular span here. You can see that the blue background color and the yellow background color are both applying. But because this is happening later in the document, it's overriding this one right here. Essentially, it doesn't make sense for us to have a style tag here if we intend for this to only apply to the web component, in that case, we would want to stick with the shadow DOM. I'm just going to backspace here and back over here. Let's keep the shadow DOM, refresh over here. Then if I was to add a span to our template here, refresh. Inspect that element. You can see that none of the styles from the light DOM are applying here because this template code is being put in a shadow DOM and then therefore, only the styles that we have in our shadow root will apply to it. Pretty simple stuff of templates here, a lot of flexibility within this, but essentially, all it takes is to create a template tag and then what we're going to do is basically find that template, look at its content, and run clone Node to create a DOM node or DOM sub tree based off of that template and then we can append it wherever we want in our web component, either to the shadow root or to the inner HTML directly. That covers slots and templates. In the next video, we're going to cover customized built-in elements. 8. Customized Built-in Elements: All right guys. In this video we're going to cover customized built-in elements. So up to this point, we have been extending the root HTMLElement. But now we're going to extend a specific HTMLElement, and this is what's known as building a customized built-in element. So what I'm going to do is remove all of this code because we are creating a whole new element and because I'm doing that, I'm going to go into my index over here and remove all of this code as well. We might as well remove anything within our style tag. We've got a pretty clean slate here. So in order to demonstrate a customized built-in element, I'm going to extend the HTML button element. So what we're going to do is create our own custom button. So starting at the same way we usually would by writing class and then I'm going to call my custom button element, literally just CustomButton. Then I'm going to write extends and instead of writing HTMLElement, what I'm going to do is HTMLButtonElement. Now if you're using a code editor or IDE, like Visual Studio Code, you can see after I type in HTML, you can see a whole list of all these different elements that I can use. The format is basically HTML, the name of the element and then element. So I can extend a heading element. I can extend an image element, an input element, a label, a link, so many different options here. So if you need to browse the different options, you're not sure what the class name is, then this really helps. But what I'm looking for is a HTMLButtonElement, so I'm going to hit that. Then let's open it up like we did last time. I'm going to write our customElements define method like we have done previously for standard web components, and I'm going to make the tag custom-button. Then as a second argument, as usual, we're going to put that class in but here's where it gets different again. We need to put in a third argument. We need to put in a JavaScript object with a key value pair. The key is going to be extends and the value is the tag of the element that we're extending so for a HTML button, the tag is of course button. If I was to write this in the HTML, it will be like that you're basically taking the word that's in-between the less than and greater than sign and putting it right here after extends. If this was a custom link for instance, let me cheat by using auto-complete there. If we were extending your HTML link element, this would change to 'a', because that's what the anchor tag is represented as in the HTML. But we're doing button so I'm going to Control Z until we get back to button. So just like we have done previously with standard web components, I'm going to create a constructor here and then of course I'm going to run super so that we can inherit the constructor function of the class that we're extending, which was HTMLButtonElement. Then what I'm going to do is head over to our index.html here and write some HTML. Let's actually create that customized built-in element. The way we do that is different to standard web components. We're actually going to use the original name of the element that we're customizing. It's going to be button. Inside this button, I'm just going to write press me and then in here to make it into our customized built-in element and not just a standard button, we're going to use the attribute of is. Then inside here, custom-button, which matches what we've written in the first argument of our defined method here. Heading back here, what I might do for this example is I'm going to put in a link. I'm going to put in a custom attribute of link and let's us just get it to go to google.com. So this would be like standard behavior of a link tag perhaps. So we would write href, and then if we click on the link, it would go to that specific URL. In this case, if I just do this right now and I head back to our code over here and I click "Press me" nothing's going to happen because this is a custom attribute. It's not going to do anything unless we write some code in our component here to handle that. So that's exactly what I'm going to do next. I'm going to grab that value so this.getAttribute. Then I'm going to put the attribute name in their link and then of course I'm going to store that somewhere. I'm going to store it on the web component itself, so I'm going to say this.link equals the value of this.getAttribute link. If I wanted to, I could also do if this has attribute like we did before, but I'm just going to assume that it does have that because I'm in control of writing this code and I'm going to make sure that it has that attribute. Then what I'm going to do is I'm going to set up an event listener. If this element gets clicked, so click "Event" then I'm going to run this code. What I'm going to do is change the Window location href to the value of this.link. What we've done here is we've taken the value inside the link attribute, stored it in this variable and then we've made an event listener for the click event on this custom button. When it's clicked, it's going to redirect the user to whatever we stored in that link attribute. So let's test if this works. Now, I'm going to refresh over here and I'm going to click "Press Me", and as you can see, we go over to google.com. Now again, not the most practical example because we could have just used a link tag for this. But let me just extend this example a little bit further. Because we have an element here that is inheriting all the original behavior of a button element, we might have to stop it from behaving like a button. One example of this would be if it was inside a form. I'm going to have a form here and I'm going to set the action to endpoint as an example. This is obviously going to send the form to nowhere because I don't actually have an endpoint. That's called endpoint. Then if I hit "Save" on that and I refresh over here and I click "Press Me", you'll see that it inherits the behavior of a button inside a form, which is to submit the form. So here you can see we're going to this endpoint here that doesn't exist. So this is of course different to if I had a link tag because we have a link tag, Number 1 it wouldn't show up looking like this because it's not a button. Number 2, a link tag does not trigger a summit of a form if it's inside of one. So what we can do is I can go over to component.js here and I can go event.preventDefault. It's saved on that refresh over here. If I click this, even though it's inside a form, it's going to prevent that default behavior and then it's going to do what we've told it to do, which is to redirect the user to whatever the address in the link attribute is. Heading back here, just to drill this home, if I was to put in a standard button that wasn't one of our customized built-in elements and say you can press me as well and then I refresh over here. If I click the you can press me as well, is going to act like a standard button and submit the form. But because we have customized this other button to prevent default and instead redirect the user to google.com, it is going to behave differently. So hopefully again, not the most practical example, but hopefully this communicates what we can do with elements. We can take some of the behavior and attributes of an element that we like, and we can modify them to our purposes. Obviously, there's insane amounts of flexibility here. You can take this in whatever direction you want, but this is not something we will particularly use in the practical section of this class, just something to keep note of because it does fall under the web component specification. We can extend specific elements here, and this is what's known as customized built-in elements. Guys, so in the next video, what we're going to do is cover the final specification that we haven't talked about yet, which is the ES modules specification. I'll see you in the next video. 9. ES Modules: Guys, so in this final video before we get into our practical class project, let's talk about ES modules, which is the fourth specification within the web components meta-specification. ES modules is essentially a way for us to structure our project differently. We can put each of our components into its own modular JavaScript file and then bring it in to another JavaScript file and then bring that JavaScript file into our HTML document. Or alternatively, we can just bring in each component into our HTML document without that file. But in the examples I'm going to show you, I'm going to import all of the components into a single JavaScript file, and then we're going to place that into our HTML document. Some projects, this is going to be very helpful, some projects is not going to make sense at all. We're going to use ES modules in the practical that's coming after this lesson. This will be something we will see in action very soon but for this lesson, let's actually demonstrate ES modules. Like I said, what I'm going to do is I'm going to import all of our components, right now there's just one but I'm going to make a second one into a central JavaScript file, and I'm going to call that one index.js. Now what I'm going to do to get us started, let's close this down so we can see the whole thing, is I'm going to move this custom elements define method here and move that into our index.js. In order for us to have access to this class right here, what I'm going to have to do is export it from here and then import it into here. First of all, because we've only got the one class here, I'm not going to do a named export, I'm going to do a unnamed export. I just do export default and then the class name I want to export. What this does is it exports only this class, so once I import it here, I can reference it directly in our code here. I'm going to run the import statement here, we're looking for custom button and then I, after the from keyword here, put the path to the file. Now, that I've done that, what I need to do is update the code here so instead of component.js, I put index.js. Like we saw in theory lesson, I need to change or add the attribute here of type module. I'll hit, "Save" on that. Let's refresh over here. We should see that we still have these buttons but if I click on the first one, which is our customized built-in element, you can see that we've got the same functionality as a standard button. Both of these are exactly the same. The reason is right here in our JavaScript Console. We've got a CORS policy error here and the reason why is because we can't really do this running the HTML from the file system. What we need to do is run a server. That's very easy within Visual Studio code. Hopefully, it's easy and simple enough in your IDE as well. If not, you can always just use Visual Studio Code and I'm just going to go down here and click on Go Live. Now you can see we're running a server. The way I can see that is because we've got an IP address here rather than a path from the file system. Now, if I press Command Option I to bring up the console, you can see no errors and if I click, "Press me," it'll take me to Google rather than that endpoint that it's looking for here. I'll close this one down in the file system and we'll just use this one. Here, you can see all of this already working. We've imported code from component.js into index.js, and then we've imported that into index.html. But this won't really make sense, and so we've created another component. I'm going to create a second component here. This is terrible naming but I'm just going to call it component-2. You know what? Just for fun, let's create another customized built-in element. I'm going to write class and let's extend the link element to create a custom link. I'm going to write custom link extends HTML, anchor element. Open that up in here, obviously I'm going to put the constructor. Then I'm going to call super as we always have done. For this, what I might do is add event-listener for click, [NOISE] this is similar to what we did before but now we've got the default behavior of an anchor tag. What I'm going to do is basically make this a confirm link. I'm going to run Confirm and ask the user to confirm they want to use Google. You want to use Google and then if that comes back as false as you can see from this exclamation mark here, then I'm going to prevent default. I'm going to prevent the user from using that link. Pretty basic example there. Then of course, I'm going to export that, and because we're only exporting one class, I can use default here. We're going to export custom link, and then we're going to go into index.js and use the same format. I'm going to import custom button from component2.js and then we'll write our defined method down here, so custom elements, define, call it custom dash link. Then we put the class name, is the second argument. Then in our extends here, we're going to be extending the standard anchor element, which is just represented as a in the HTML. If I then head over to my index.html, we're already importing all of this code. All I need to do is simply write an a tag here. Let's get it to go to Google again. Then of course, in order to take on the functionality of this customized built-in element, I'm going to have to write is and then inside the is attribute, put in the name that we specify which is custom link and then I'm just going to right-click me to go to Google. Hit, "Save" on that. Let's refresh over here. You can see the link there. We didn't even have to refresh because we're using Live Server, which is just a feature within Visual Studio Code. This is called hot reloading. Then if I click on this is going to ask me if I'd definitely want to go to Google. If I click, "Cancel", it's going to prevent the default behavior, which is directing me to that address. But if I click on it and click, "Okay," it's going to direct me there. The functionality within the actual component itself isn't the point here though. The point is our ability to export classes from JavaScript documents into other JavaScript documents and into HTML documents by using ES modules. Hopefully, that's a cool concept for you guys that you've just learned. In the next video, we're going to start building this out as a practical application. In that video, maybe multiple videos depending on how long we go for, you will see us using ES modules in action and so you'll see it in practice a little bit more in the next few videos. That's basically all the four specifications for the web component meta-specification. Let's actually put this into practice in the next video and create our own custom project. 10. Web Components in Practice Part 1: All right, everyone. Welcome to the practical project-based part of the class. I get the feedback quite regularly from you guys that you want to see more practical examples. In this course so far, we haven't seen too many of them if I'm being honest. Without having a few web components working together and without a use case, it's hard to demonstrate actual practical use cases of web components and a lot of these specifications you don't need to use at the one time. For instance, in this project that we're about to create, I'm not using the Shadow DOM at all because I don't feel the need to use it unless I'm trying to keep my Shadow DOM separate and what I mean by separate as in separate CSS styling wise to the rest of my project. If I'm in control of my own project, then I don't really feel the need to have to create a Shadow DOM because yeah, maybe I do want the styles from the Light DOM to affect the Shadow DOM. Mix and match whichever features from whichever specification makes sense for you. I hope that you've learned a few good fundamentals in this class, but I wanted to include a more practical project-based lesson for you guys. This will probably have to be broken up into multiple lessons, so this might show up as multiple videos within this Skillshare class. But what I want to do is create a practical example, the most practical example I can think of and use that practical example that we saw earlier in the class with the e-commerce example via Shopify to create our own e-commerce-inspired little project, and if you want to take this project further, use it as inspiration for something that you want to use in the real world. Feel free to do so. But rather than, yap on about it anymore, I want to just jump straight into it and let you guys follow along with what I'm creating here. For this project, I'm going to commit everything in Git so you can look at this code later and compare your code to it. That's been a criticism of some of my classes in the past as well, is that you guys haven't actually been able to look at the code itself apart from in the videos. I've listened to you guys on that as well, and so I'm going to be committing as I go and you guys can look at this code on GitHub after this class is published and compare your code to mine. Without any further ado, let's get into it. I'm going to create a project folder here inside the code folder where we created the other folder. I'm going to call this one shopping cart. Then bring up Visual Studio Code, then grab this folder, drag it into Visual Studio Code, and therefore, I know I am opening that particular folder. Obviously, I'll extend this out to the full width of your viewing. There we go, and let's start with our boilerplate content. So I'm going to create a new file with index.html. Same thing as I've done in the past, use the exclamation mark to output a boilerplate HTML document. Let's call this shopping cart. Then what I'm going to do is copy in some assets, CSS and image assets because this class is not really on CSS, and of course I'm not going to go and get you to get your own images, so I'll bring those in right now. Let's just have a quick look at it so you know what's going on. I've just got a styles.css file here and it's just got a bunch of different code. This one's redundant. But we've got a bunch of different code in here just to style our project nicely. Again, we're not really focused on styles in this class, but it's much better to have a project that is styled nicely than an ugly one. So I've got that in there and then I've got images here, random products. These are random product images as you can see. They all look visually similar, but there's no relation between Coke Zero, and Hemp Seed Oil, and Red Curry Paste. A bit random, but we just needed some placeholder images for this project. I'm also going to create a JavaScript folder. Start by clicking "New folder", "js". Inside this js folder, I'm going to create an index.js. What I'm going to do here is because we're going to be building a shopping cart, I'm going to create an object on the window object. It'll be a global object called cart, and then I'm going to open up an empty object, and I know that in this object I want to have an array of items, so I'm just initializing that right here. I'll hit "Save" on that, and this will probably be my first commit. I'm going to bring up a terminal here, run git init, git add., and then git commit initial commit. Clear that, and so what I'll do is I'll try to commit every so often so that you guys can see the progress happening and can refer to the code if you want to. We've got this **** DS Store here. I'm just going to discard that, don't really need that. Back to our code here, let's take our first step to creating this project. Actually, before we do that, let's actually click "Go Live". Now you'll see we're running our index.html file. We also need to load in those styles, so probably should have done that before my initial commit. But here we go, link rel="stylesheet" and then the path to that style sheet is css/styles.css. Now before we build any functionality, I'm just going to create some HTML here, so let's create a div with the class of container, pretty standard stuff, and then h1 shopping cart. Then what I'm going to do is a div with the class of products for sale, and then inside of here, what I'm going to do is create four divs, so div times 4. Let's hit "Save" on that. Let's look over here, then you can see if I click the click "Inspect on it" and see products for sale. We have all of the CSS ready to go for when we insert these product elements in this div right here. Before I do anything, let me give you an overview of what we're trying to create here. This will be our finished project. As you can see here, we've got four products that we can add to our cart, and if I click on the Add to Cart button underneath any of these products, it'll add that item to our little cart here. If I click on that button again, it'll increase the quantity, if I go click on another product's Add to Cart button, it'll add that line to the table here, which is auto updating the total. Think all of these are about 10 bucks, so it's pretty easy math, and I can also update the quantity via these quantities selectors as well, and that'll update the totals as appropriate. I can also go in here and set this to 10 if I want. Then I can set my own specific quantity. The theory behind this is if it was a real shopping cart, I could click a button and it would head to check out with that selection. For our purposes, if we refresh the page, we'll lose our cart. We could write some code to persist that into local storage or session storage. But for the purposes of this video, I don't think we really need to do that, that could be an improvement that you make in future. The final thing that it does is, if it's at one and you try to reduce the quantity, it'll just remove that item from the cart. If there's no items in the cart, the whole table will disappear. So that's what we're building. I might leave that tab open to the side there, head back over here, and let's get to work. Actually one more thing before we get started on this, I want to actually overview how are we going to break this up into web components. Here you can see we've got each of these products here and the same functionality exists on all of them. If I click the Add to Cart button, it'll add that particular product to the cart, so each of these products is going to be it's own web component. Then as you can see, if I refresh up here, click "Add to Cart" we get this other section added here, which is our list of cart items, the cart items table. That's going to be a component as well. Then finally, within that cart items table component, we have another component which is our quantity input selector here. So anytime I click plus or minus, we'll set a specific quantity here, let's set it to zero. See what happens, it disappears. That is going to be a component as well. Let's start with the product component. Let's head over here, and then head over to our code editor. In our js folder, let's create the file for that component. I'm going to call it product.js. Let's start with the boilerplate. I'm going to call this class product element. Of course, we're going to extend HTML element as we've seen throughout this class. Then before I write any code inside, I'm going to just export it straightaway. Because we're using ES modules with each component contained in its own JavaScript file, I am going to use export default and then just write the class name after that. Hit "Enter" on that, then switching over to my index.js file, I'm going to put my import at the start of the file products element, and then right from the path to that file, which were already in the JavaScript folder, so it's going to be.product.js. Then we need to put in a defined method here, obviously, custom elements define. I'm just going to call that product-elements as the first argument, and then bring in that product element class. I also have to import that index.js file into HTML. Let's not forget that. Script src="js/index.js" and then not forgetting that type attribute of module. Hit "Save" on that, refresh over here, and see if there's any JavaScript errors which there aren't. Perfect. Let's head back. Instead of these four divs what I want to do is do four product elements. Let's hit "Save" on that and head over to here. As usual, I'm going to add the constructor method and then run super. Now we have to do some thinking about how we want to construct our custom web component here. If I look at the code here we're going to bring in this web component four different times. I don't know if I want to write my inner HTML inside each of these because it's going to be quite repetitive. What I might do is I might put the HTML structure inside the web component class definition right here. That way it is the same for every web component which is what we want and that'll keep our code clean. What I'm going to do is I'm going to write this inner HTML. This structure is based off of the CSS that I've already set up. It's taking advantage of classes already in the CSS. I'm going to create a div with the class of image container. Close that and then inside I'm going to take the image with a class of product image. Then I'm going to put in the SRC which as soon as we get here we should start to think to ourselves, this needs to be a dynamic value. If it's a dynamic value then we can't really set something explicitly here because then the product image is going to be the same for every product. Obviously, the structure of each product element is going to be the same but the product is going to change, and therefore the product image. Therefore the title and the product ID is all going to change. Now we'll get to each of those as it comes. But to start us off what I want to do is create a custom attribute at least for the image. That way we can pass in the image in each of these web components. We saw this before in the class when we looked at custom attributes now we get to use it in practice. What I'm going to do is put in an image attribute and let's have a look inside our folder here. We make that the path to the image, image/coke-zero.jpg. Let's copy that over to here and here and then we'll update that. Second one will be hemp-seed-oil. Next one will be magic-touch-body-cream and then the final one will be red-curry-paste. Hit "Save" on that. Then what we can do is heading over to our web component here. We can set the image on the web components. We're setting a variable attached to this web component to like we did before, get attribute and we're going to get the value of that image attribute. Then given that we have that value we can then go into here and insert it right here into this SRC. I'm going to hit "Save" on that, refresh over here and let's see what we've got. They're going to console, no errors. But you can see it's not coming across either. I'm suspecting that this is because of some silly error. Yes, it is a silly typo. Should be product element not product elements. Just go through and get rid of those S's. Hit "Save" on that and then if we refresh over here, we actually don't even have to refresh because you can see it working for us. Let's see if we can still view things. Yeah. Let's put this down the bottom and then you can see we've got our lineup of the four product images. We've got the same structure for each product element which we store in here but we are passing through the dynamic value of where the image URL is and then we're putting it into our template structure here. But of course, as I mentioned before the image URL is not the only dynamic content that we need to pass into this product element. We need the title slash the name of the product. We need its price and we need its ID. Let's create some attributes for that as well. Let's continue with the next in the visual hierarchy. Let's continue with the name. I'm going to do the exact same format I've done here. Create a variable called this.name and then get the value of the attribute name. Obviously, this hasn't been created yet. I'm going to go into here, I'm going to close down this so we can see this better, and then I'm going to create a name attribute for all these products elements. Then this one's going to be coke zero. I'm going to put in brackets, can. Then here hemp seed oil and then here I'm just going to call it body cream and then here red curry paste. Hit "Save" on that. Let's go over here and then we actually need to use that name somewhere in our code. I can go down here and then I can go just like we've done with the image, create a h2 inside this h2. I can slide in this.name. Refreshing over here you can now see the name of each product underneath the product image. There's a little bit of CSS problems here and that's because it's not supposed to be inside the image container. Let me fix that. It's supposed to be outside of this div. I hit "Save" on that refresh over here then you can see we haven't got that issue anymore. Sweet. That makes up our visual hierarchy. But the other thing we need to do is actually store some data in this product element for when we click "Add to Cart" because depending on which button or which element we click "Add to Cart" on is going to have a different value. The way I'm going to do this is instead of adding it to the inner HTML I'm going to create an element and then append it to the DOM. The reason why I'm going to do it this way is so that I can attach an event listener to it before I append it to the DOM. What I'm going to do is create an element for the button. It's just going to be button and then of course I've got to store that somewhere, so I'm going to just store that in the constant called button. I'm going to set the inner HTML of the button to add to cart and then I'm going to add an event listener. Actually, let's comment that out for now. Get back to it in a second. I'm just going to append that to add document first. Hit "Save" on that, refresh over here and you can see we've got our Add To Cart buttons and their style, just like in our example because of the CSS we've already gotten there. Now for this to work, we need to be able to know what the name and the id of the product that we're adding to the cart is? Because this is currently doing nothing, and that's where we're going to pass in the id. But we also need to know what the price of each product is, so we can add that to our cart table as well. We're going to pass in id and price. Let's create attributes for them as well. I'm going to start inside the component, and I say this.id equals this.getAttribute, not animations attributes, id, and then this.price, this.getAttribute price. Then I'm going to go over to the elements here. Let's add in an id for each. The first one we're going to do 1, 2, 3, and 4. There's only full products, so we only need simple ids. Then I'm going to add in the price. We're going to keep it simple and make each of them $10. We'll just pass these in as digits now and convert them into integers or floats later. Now heading back to our web component, this is the part where we actually build in some functionality and get a little bit more complicated. As you can see here, I've got a commented-out EventListener. What I want to do before we build that out is create the method which this EventListener is going to trigger. Outside of the constructor, let's create our own method called addToCart. In addToCart, what we're going to do is construct an object with all of the attributes that we want to add to a cart item object. I'm going to create a const item object and you should see what I mean by this very shortly. I'm of course going to add a key of id, and I'm just going to take the id that is stored in this web component. I'll close this down so you can see more. Then I'm going to take the name and that's going to be equal to the name as set in the web component. Quantity is always going to equal one because the Add to Cart button is just going to add one by default. You can add in a quantity selector into this element later if you wanted to as an improvement. But for now, anytime you click "Add To Cart" is just going to add one. I'll fix that to the number 1, and then price is going to be this.price. Let's have a look. When we actually run this, all I'm going to do to start with just to see it is to run console log, and then itemObject, the object that we just created. Up here in addEventListener, I'm obviously going to look for the click "Event", and then I'm going to run this addToCart method, and then I'm going to bind this, so that when I'm down here in this method, anytime I reference this, I'm going to be referencing the web component, not the event. Hit "Save" on that, refresh over here. Let's open up our console, and if I click on "Add To Cart" on the Hemp Seed Oil, you can see that it sends through the data. We can see that the product id is 2, the name is Hemp Seed Oil. We're adding one and the price of each is 10. If I do it on the Coke Zero Can, I get a little bit of different data. If I do it on the Body Cream and Red Curry Paste, you can see I get a different item object every time. That's confirming that we're sending through an object with all of the information that we need. Now what we need to do is write a little bit of logic in order to add it to this items array within our cart object. Let's go over here and I'll show you how we can access that cart object. Because it's on the window object, we can type window.cart to have a look at it or we can simply just write cart because we're already in the context of window. As you can see, no matter which way we do it, we can see that we have an object with an empty items array. What we want to see when we output that again, after we add to cart, is that we have a new item in that items array. I'll clear that. Let's go and do that right now. I will comment out that console log, and what we need to do in most cases is add that object simply to that item's array. We're going to do window.cart.items.push to add that item object to the item array, and then we're going to put in the item object as the argument there. Let's hit "Save" on that. Refresh over here. If I type in cart right now, you can see we have an items array that's empty. Let's get a bit more specific and say cart items specifically. We have an empty array that's clear to see. Let me just do that one more time. We have the before, then if I click "Add To Cart" and we type in cart.items now, you'll see we've got one object in our array, and if I look inside, it is our Hemp Seed Oil with the quantity of one. If I click on "Add To Cart" on Body Cream and then I go cart.items, you should now see that we have two items in our array with each of those products. Now we've got one issue that I don't know if you thought of this or not, but you will soon find out that this is an issue. If I add another one of the same product that we already have in our cart, let's say Hemp Seed Oil again, and then I go over to here and go cart.items, you'll see we have three items in the cart, and we've got Hemp Seed Oil twice, but with one quantity each. We want to see one item in the array for Hemp Seed Oil, but we want this number to be two. We need to write a little bit of extra logic for that. Let's head back over to here and let's use the find method within JavaScript to figure out whether the item already exists in the array. I'm going to create a constant item in cart. Then we're going to look up window.cart.items, then we're going to use the method find passing through the item. If the item id is equal to the item object id, then we have found a match, and that's going to be the object. Before you write anything else, let's just do console.log item_in_cart. It should either come through as undefined or some other null value if it can actually find anything, but if it does find something, we will actually get the object itself. Refreshing over here. Let's click "Add To Cart" and it'll come up as undefined. But if we click "Add To Cart" again, you'll see that we have that existing product already in the cart. If go cart.items, you can see we've got the same item twice in the cart. We need to fix this. What we're going to do is run if item_in_cart, then all we want to do is take that item that's already in the cart and just increase its quantity by one. But if it's not in the cart, so if it comes up undefined, for instance, we're going to then run this code down here to push that item to the cart. Let's save that and let's see if that fixes our issue here. I'm going to add in Hemp Seed Oil, I'm going to add in Body Cream. Let's have a look at our cart items now. We have one Hemp Seed Oil and one Body Cream. If I add one of the same product again, the Hemp Seed Oil and then let's have a look at cart.items, you can see we've only got two items in our array now, and if I click on them, you can see by clicking "Add To Cart" on the Hemp Seed Oil, it hasn't actually created a new item in our array, but it's simply increase the quantity of that existing item, which is exactly what we want. Heading back here, that is essentially all the functionality that we need, specifically from the product element. Just to summarize at this point before we move on, we have created a product element web component. We have passed in four parameters. Inside the web component itself, we've got a standard structure that is common to all of the product element web components, and a common functionality of addToCart, which we can see right here. I'll remove that console log because this is all working, and now you can see, even though we don't have cart.items visually represented on the page yet, we are able to add items to our cart items array. The cart object here is basically our state object, and what we're going to do is use that cart object to create our cart items table in the next video. As promised, I'm going to commit this code and discard. I probably should have put that in the get ignore. Unstage, go in here, discard that, and then I'll commit our changes to index.html, index.js, and then, of course, the creation of product.js. I'll stage all of those changes and I'll add the commit message, create product element web component. Sweep. I'll hit commit on that, again, discard those changes. I'm going to break this up into another video. In the next video, we will look at the cart items component. 11. Web Components in Practice Part 2: All right, guys. In the last video, we set up a index.js, which has our cut objects with the items array, and we learned how we can populate that items array via this product element that we created in the previous video, and obviously, that has a visual component to it as well. We have each of these products elements with these add to cart buttons. If I click on any of these add to cart buttons, it'll update our cart.items that we have here, but that is not actually visually represented on the screen as of yet. That's what we'll do in this video. We're going to create a new component called cart items. Let's do that right now. Let's head into our JS folder, create a new file called cart-items.js. Let's get our component boilerplate code going here. Cart items is what I'm going to call it, and we're going to extend the HTML element class. There we go. I'm tempted here just to write custom elements defined, but remember we are using ES modules. Instead of that, what I'm going to do is export cart items, and then I'm going to do my defined method here in the index file. Define cart-items, cart items here. Lo and behold, Visual Studio code knows what I'm trying to import and automatically adds the input statement up here. But if your code editor doesn't do that for whatever reason, you can just simply write this code. It's the same format as what we had before. Sweet. In order to get this cart items component onto a document, I'm going to simply head over here inside our container, so it's all contained, I'm going to add in our custom web component of cart items. It's going to automatically put in the closing tag there. Now, if I refresh my page over here and there's no errors, I can reliably tell that we have that on our document here. If I go in here, you can see we've got our empty cart items web component here. Let's actually add some stuff to it. Let's close down products for the time being. Let's go over to inside our class definition here. Obviously, as always, we're going to need our constructor, and inside our constructor we'll put in our super method. Now, unlike all of the other components we've made in the past, I'm actually going to leave it there for the constructor method. I'm going to put all of our rendering inside another function called render. Now, why would I do that? The reason why is because when we add a new product to the cart via this product element, we're going to need to render for the first time or re-render our cart items table. It's going to be handy to have our render function here so with that we can re-render whenever we need to. All of the code inside this render function won't be run when the cart items component is included on the page, like we have here, is going to actually run once we specifically ask it to render. This is going to be a lot of code and I'm not going to go super in-depth into all of it, but I will explain as I go. What I'm going to do is create a starting point for this cart items component. I'm going to set the inner HTML to nothing when we run render. Now, the intention of this is when we run render in the future, we need to wipe out the content we had before, otherwise we could have some of the old inner HTML inside our component when we have to re-render the cart. Then what I'm going to do is construct a HTML using document create, the second method we saw in the earlier video on three ways to create HTML within our web component. I forgot to put in the name of the variable here. I'm going to call this one table container. Then I'm going to add the class of table container to this table container div, and then the second element I'm going to create is called table. This is our actual cart items table. Going to create a table element, assign it to the table constant here. Then what we're going to do is we're going to cycle through all of the cart items. I'm going to run a four each on these. I'm going to have two arguments here, one for the element itself so that it'll be the item itself, and then the second will be the index. Let me just get back to that in a second before I go into that deep code. What I'm going to do is table_container. What I want to do is append our table to that table container, and then we're going to append the table container to our web component. There's a bit going on here, but bear with me. What we're doing is we basically created if I save and then refresh over here. Now, it's not actually rendering because, remember, this is in the render method, it's not on the constructor like we had before. What we're going to have to do is run some code to find the web component that we're looking for, which is cart-items, and then run the render method on that. If I go back here now, you can see that inside of our cart items, we're going to have a div with the class of table container and inside we're going to have the table. That's what we're doing here, we're creating this HTML structure. Now, what we're going to do is for each item in the array, we're going to add in. I'm going to grab the inner HTML, and I'm going to use the plus equals to append to our string here, let's open this up, and I'm going to add in a row for every cart item in our cart.items array. Let's see this in action. If I hit "Save" and I go over here, and let's rerun that render method, go back into here, find our cart items table container and inside our table, you'll see there's no rows in the table yet, but that's because we actually have nothing in our cart.items array. If I go cart.items, there's nothing in there. If I was to add, let's say, two items to our array, let's have a look now, cart.items. We definitely have two items in our array now. If I was to run render on cart items, head back into elements here, go inside our table, you can see we've got two rows. Now, obviously, we need to populate each of these rows with the data for each of those cart items, and that's what we're going to do right now. Again, there's going to be quite a bit of code here but bear with me. We need three table cells. In the first table cell, we're going to put the item name. So of course we've got access to each item as we loop through it, because that's what we've set here. I'm going to say item name. Then in here we're going to have a quantity input. We'll get to that a little bit later. In this one, we're going to have $sign and then we're going to have our item dot price. We'll fix this up in a second because this needs to be the line price. So if the quantity is greater than one, then we need to update this. Then what I'm going to do is just a little bit of formatting. The first td. I want the text aligned to the left, but that's default, so I'm not going to change anything there. In the second one, I'm going to make the text align to center. Then the last one I'm going to set the text align to right. Now if I hit "Save" on this, I think it automatically hot reloads anyway. I hit "Add to Cart" on two of these buttons. Then I run the render function. You can see here we have the beginnings of our table. We have the name of the product that's in the cut and the price. But of course, if I hit this one again, we only have two items in the array. It doesn't update the quantity or the line price. So before we actually create the quantity selected here, I can just put in the item quantity. While we're here, let's fix up this price, the line price. Let's just call this line price. That's going to be equal to making sure we put this inside the loop. The line price is going to be equal to the item dot price times by the item quantity. I'll hit "Save" on that. Refresh over here, let's add some items to the cart. I just pressed "Add to Cart" on hemp seed oil, then on body cream, then back to hemp seed oil again, so we should see two items in our array. If I go down here, you can see our selection has been saved. If we run render, we can see that it's showing up in our table down here. Now of course, every time we're having to run the render function ourselves, we obviously don't want to do that. We're not going to expect the user to go into their console and know what command to run. So let's set that up so that it runs in our product dot js. After we add to the cart, so right here I'm going to add a line. We can literally just take this line and place it there. Hit" Save" on that, refresh over here. Every time we click "Add to Cart", it'll re-render the cart. This annoys me a little bit. I want to store this web component in its own variables so I'm going to create a variable for this. I'm going to go up here and do this dot cart items equals and then take that element of the DOM. This I feel like is just a little bit cleaner. Again, don't have to do it this way, but I like it a little bit better. The next part I'm going to do before I move on to coding up the quantity input is to create the table footer. So if I look at it now, we don't actually have the total and for that we'll create a table footer. I'm going to just remove that there. Let's go into cart items dot js. We're already there. Then after this code, what I'm going to do is create a new element called footer document creates element. This is going to be a div. Then I'm also going to have to figure out the cart total. So I'm going to have to use a little bit of a complicated method here, window dot cart dot items, and then run the reduce method on it to tally up everything. So total item, if this confuses you, just study up on the reduced method, but it's a way for us to add all the values up to create a total. What I'm going to do is because the prices are going to come through as strings, because we've put them through as a string in our HTML here, we just need to pass them into integers. Now, we might want to pass these into floats if we had decimal values but for this project, we're not going to have any decimal values. The lowest amount is going to be one and it's going to go up in one, so nothing is going to be $2.75 or anything like that. We're just going to keep it simple. It's going to be $10 or $20. Actually, everything in this project is going to be $10 so we're keeping it very simple. But just so you know, you might need to change this. But for our purposes, everything's going to be an integer, so we can do pars int. Let me open this up a bit more for you. Then in the pars int, we're going to take the total add item dot price times by the item dot quantity, which is of course the line price, which we calculated earlier up here. In the second parameter we're going to put through zero. So then what we can do is set the inner HTML of the footer to, let's put in some HTML code here. Let's put in another table, which is going to line up with the first table. Going to give this a class of cart footer. Then we're going to put in a new row. Then in here, we'll put in a cell, which is just going to have the word total. Then we'll put in another cell for the total itself. I'm going to put in the style of text, align. It aligns with the line price over here. Then in here we can put our dynamic value. So I'm going to put in an actual $ sign first, and then $ sign curly braces to put in our cart total, which we just calculated. Then down here, this dot append child footer. I'll hit "Save" on that and then we'll refresh over here. If I click "Add to Cart", you can see we've got our total showing up here. I'm not able to reduce the quantities yet, but you can see we can at least add items to the cart. Our cart items table is updating, complete with the total so that just about covers us for the cart items, table components here. The last component is going to be the quantity input component, which is going to be nested inside of the curt items component. I'm going to break that off into its own video, so we'll cover that in the next video. Then we'll make our final modifications to complete this project. I will catch you guys in the next one. 12. Web Components in Practice Part 3: In this third and hopefully the final video in this series in which we're building this custom shopping cart functionality, I'm going to code up the quantity input. Now one thing I forgot to do before we closed out the last video was to commit this code for you guys, so I'm going to do that now. I'll just discard this DS store again and just having a look at changes here. I always like to do this before I commit. I am going to stage all of those and the commit message will be create cut items. That's committed. Discard that one again. Let me close down some of these. I'll probably have to open them up again, but there's too much going on here. Let's go into our js folder here and create the quantity input component. Again, starting with the boilerplate code, you guys should be pretty familiar with this by now. Quantity input extends HTML elements. Then we're going to export it straightaway. Export default, quantity input, then go into index.js. I'll write my custom elements define method first because I know it's going to import it automatically. Let's call this with the extended name quantity-input. Magic VS Code has put that input statement in there for us so I can close that down. For this particular element, what I'm going to do is basically construct it all in the constructor, like we've seen previously in this class. There's going to be quite a bit of code here and if you're wondering where I got all the ideas for this, I actually took the dawn theme from Shopify as inspiration. A lot of this code is inspired by the same code within this component within the Shopify dawn theme. If you want to reference this against that, or wonder where my thought process it's coming from, you can have a look at that theme. As usual, we're going to put in the super method here and then what I'm going to do is, let's write all the HTML inside of the cart-items.js here. Scrolling up here, all we're doing so far is just writing the item quantity as it stands in this second column here, but of course we can't update it there is no input there. Let's replace this with our new custom element, quantity input for the closing tag. I'm going to create all the inner HTML within the tags itself. We didn't do this obviously for the product element here, because we didn't want have to write it out four different times. Here, in the case of quantity input, we're going to be writing it out every time there is a row in the table, and each row is going to look virtually the same. The only difference being the data that comes through in the item. We can place it here and it's still going to be DRY code. Here, I'm just going to put a class of quantity. If you see me writing classes like this, it's related to the CSS, so don't worry too much about the classes. Then let's create our two buttons, our minus button, our input itself. Obviously there's a lot more stuff that needs to go in here, but I'm just going to start with the basic structure first. We've got input button. Let's just have a look at how that looks. You can see the basic structure there, but of course we're going to need to put in the minus in between those button tags and then the plus in-between these button tags. For the attributes, we're going to give it a class, we're going to put it on both buttons here, class of quantity__button. Going to close this so we can see better. Type button. We can put that on both, type button, and then the name is going to be based of wherever it's minus or plus. I'll escape out of that and then I'll put minus into the first one and then plus into the second one. Let's hit "Save" on that and see what that looks like so far. It looks like we've got a hit "Save" on that. It shouldn't be that relevant anyway. Hit "Add to Cart" and as you can see now, we've got our styles coming through for the plus and minuses. We need to add some code here inside our input tags here. Let's add some attributes. We want the type to be numbers so that people uses cart insert text here other than digits. The value is going to be equal to the item.quantity. The minimum is going to be zero. We don't want any negative values. We're going to give it an ID and the ID is going to start with quantity and then we're going to put a dash and then use the index to differentiate each of the inputs. Then I'm going to put in a data index here as well, which is going to be the same thing. We're going to use the I as the data index. I'll hit "Save" on that. Refresh over here, click "Add to Cart" and as you can see, this looks like it's working. The only thing that's not working is we can't add and subtract. If I change this to two, it's not actually going to change the two. If I change this to zero, it doesn't actually change the zero. That's where we need to start actually coding up our quantity input here inside of its class definition. Let's set some things up in our constructor. I want to first bring the input into its own variable. We're going to find that inside of the DOM, within our web component. Then what I'm going to do is create a onButtonClick, which is going to be the callback for our EventListener. I'm going to add in the event as the one and only parameter. I'm going to prevent any default behavior, and then I'm going to write some logic here to get this to work. The first thing I want to do is grab the previous value, the value before we change it, and that's just going to be equal to the input value at the time of this running. This.input represents the input element itself, and we can of course grab the value of any input by just looking at its dot value property. It's going to grab the value before we change it and then we're going to have a look at the event and look at the event targets. The event target in this instance is going to be the button that we click. Then we're going to look at the name of the event target, ie, the button and if it is plus, we're just using a ternary operator here, to increase to run step-up on the input. Step-up is just a method that allows us to increase the value of this input by one. If it's not plus, which means it's definitely minus because there's only two options, we're going to run step down instead. Now if I hit "Save" on that, now that we have our event handler here, let's actually attach that up to all the buttons. For this because there's multiple buttons, we're going to use a querySelectorAll for every button, which is on the two and the end but we still need to use querySelectorAll. That's going to give us an array and then we need to loop through them. For each button, we're going to add an EventListener of click. If the button is clicked, then we're going to run that event handler on button click. Just like before, we're going to bind this so that this represents the web component and not the event. Remove that semicolon there and put it there. If I hit "Save Now" and I refresh over here, click "Add to Cart", I can get this to step up and down, but what's not happening? Actually, if I press minus, it's going down. That's not very helpful, is it? I think that is probably related to this typo here. This is not a comparison, this is an assignment, so let me make that a proper assignment. Let's go back over here, click "Add to Cart". Now, if I press down, it should work. Now the thing is with this is, the value inside of this input is going up and down with these button presses, which is good, but it's not actually changing the value in our data structure. Let's just say I put this up to seven, and then let's actually go to our cart items array and have a look inside, you can see that the quantity is still one. We can increase the quantity by pressing the add to cart here and as you can see, it's updated to two now. Then if I go into cart items, I can go here and it's quantity 2, but putting this up and down doesn't actually change anything. Let's fix that right now. The way I'm going to fix that is by using a custom event. Now why am I going to use a custom event here? Well, I want to trigger a change event on the quantity. What I'm going to do is I'm going to pass through the line of the item I want to change and its new quantity to a method, and then that method is going to update cart.items. Now we can put this method inside of quantity input, but I think it makes more sense to put it in cart-items because it literally is updating cart.items, the object that this component relates to. I'm going to put it in here before render. What this method is going to do, I'm going to call it update quantity. It's going to take an index which is going to relate to the line item here. As you can see here, we're cycling through an array and we are storing on the quantity select what number in the array it is. I can utilize that to figure out the line number. Then the second parameter is going to be the new value, which I'm just going to call value as the parameter. Then when this is run with those two parameters, what I'm going to do is then look up the item via its index, and then simply take the quantity of that item and then change it to whatever value we pass through. Then when that's updated, what we're going to do is re-render the cart items table, because obviously we need to re-render the line price and we need to re-render the total when we're making these updates, so it's better just to re-render the entire table. I'm going to hit "Save" on this, and then what I'm going to do is make reference to cart items. As we saw before with product, when we reference cart items, I put it into its own variable first before running the random method on it. Because we're going to be running a method on this, I want to do something similar, set this up in the constructor, so I'm going to put this up here. Then when we run on button click, we're obviously going to be changing the quantity value. Now because we have assigned that web component to this variable here, I can go this.cart items and run the method on that web components, so I can do update quantity, and for this I just need to figure out what the index is. I'm going to go this input data set index, so that's going to allow me to access this right here, data index. That is available on the data set array. I'm going to grab that index. Then as the second argument, I'm going to put this.input.value. I'll hit "Save" on that. Now let's refresh over here. Click "Add to Cart". Now as you can see, as I decrease and increase this, it's going to re-render the whole cart items table and change the total and the line price. Now there's one more thing missing here, and that's if I change this to two, it's not going to change anything. If I go back to this, it will change it, but if I put in a specific value, it's not going to change that. Let me refactor this code a little bit more. What I want to do here is I want to create a custom event. Now remember when we talked about custom events earlier, it didn't really make sense because we were just linking a click and transforming that into another custom event. But this time we've got two ways to change the input value, ie, the quantity in this case. We can set a specific quantity value, or we can use plus and minus buttons here. What I want to do is combine both of those events into its own event, which I'm going to call a change event. That makes sense. Let's see it in action. First of all, I'm going to create this custom event. I'm going to go this.changed event is going to be, as we saw earlier with creating custom events, we create a new event, give it a name, and then this time I want to add a JavaScript object for bubbling. I want to set bubbles to true. What this is going to do is it's going to register a change event on the parents web component as well, because I want it to be registered on here. Then in here I'm going to create an event listener for the change event on this particular web components. I'm going to put in change here, create a function here. I'm going to grab the input. It's going to be the change event here. Then what I'm going to do is I'm going to put the update quantity here. Instead of having to run this method when we run the button click event handler, and then also the event handler for when we change the element explicitly, like we did before, like this, I'm going to dispatch this change event on both the button click and on that input change. Let me just write here this, and I'm going to dig into the input because we need to use some of its attributes there. Then I'm going to dispatch this custom event, so this change event. Then because we're looking on the specific input, instead of having nothing here what I can do is pass through the event, and then instead of looking at this input, I can create a reference to the input here based off of the event current target. Sorry, I forgot to put the assignment in there. Then we can remove this dot from each of these. Let's hit "Save" on that and let's see if that works. I'm going to open up my console just in case there's errors. Add to cart. Put this up, put this down. It's not working because we are checking for the events on the web component not on the input. What I'm going to do is just add an extra parameter here with a JavaScript object, and I'm going to set bubbles to true. That way, the change event is going to bubble up to the quantity input component itself. Let's hit "Save" on that, refresh over here, and let's see if this works now. You cannot set properties of undefined setting quantity, let's have a look. Let's have a look at what is coming through here, console log input. Let's have a look. That's putting through the web component itself. That must be because we're bubbling up to the web component that is triggering the change and then the event current target in that instance becomes the web component itself not the input. We can fix that by just using our query selected to go in and select that input. Let's hit "Save" on that, refresh over here, hit "Add to Cart", and then we've got the same result as we had before. This is actually updating our quantities and updating our cart as well. As you can see, this is not working. Let me just change this to two and then hit "Tab". As you can see, that's changing it to the explicit value. As you can see, because change is already an event on that input, we are also taking advantage of the existing event that would be fired when we change it explicitly. We're basically adding our button click into the change event. This is something that happens automatically, we don't even have to register a change event when we change this value explicitly, this just happens automatically. This is a good example of how we can tag on a custom event onto something that already exists. Now just to clean this up, we've got this previous value here. It's assumed that if we click on a button we are changing a value, but just to be sure I'm going to add an if statement here. We're just going to check if the previous value does not equal the new value. If I save, refresh, we should get the same result which we do. That's just an extra check. That's basically it guys. We've got our three web components here. We've got our product element, and if I click "Add to Cart" on any of these, it will interact with our cart items component. I can use these quantity inputs to change the prices here. Actually one thing we didn't do is if it hits zero we want to remove that, and if the total hit zero we want to remove the table altogether. I thought we were done but we've just got one more thing left to do, this is why you should always play around with your projects and see. Let's go into cartitems.js, and let's write in some extra code here. I'm going to scroll down to the bottom. The first thing I'm going to do is if the cart total is zero, well, actually I'm going to do the inverse. If the cart total is not zero, then we're going to render the footer. But if the cart total is zero, then this code will not run at all, which means we won't have a cart footer when this render function is run. I'll save on that. Then what I'll also do is I'll put a similar check up here. Just after line price here, I won't add the line to the table unless the item quantity is greater than zero. If it's not zero, then we will run this code, so grab that, and put it here. Hit "Save" on that. Now when we add something to the cart, increase its quantity, but then go back to zero, that will remove that. Let's just say we have multiple ones here and we remove all of one of them from the cart, then the whole cart table does not disappear, it's just that line. We can add all of them, remove some of them, increase the quantity. If this was a fully functioning app with like actual checkout functionalities enabled, we could have a button here that sends that cart array to a checkout page, and then the user would be able to complete that order. That concludes this little class project. Let me just commit this code before I forget. Again, discarding the DS Store. Let's have a quick look at what we changed here. All looks good. If you spot any errors with any of this, just let me know. Comments below but I'll stage all of those and then complete project with quantity input. I will discard this one again. That's pretty much it guys. I will have this code on GitHub for you. Just look for it on my GitHub page, which is github.com/christopherdodd. Let me get rid of this. Check my repositories, see if you can find it in there. Any questions. Leave them in the comments below. I hope you've enjoyed this practical application of using Web Components. Hopefully in this class, seeming we've started with the theory and we've gone into the practical, you can start to solidify what Web Components does and why it's important. I will see you on the next video where we will conclude the class, and I will talk to you about how you can extend this class project even further if you want to. Thanks for watching, I will see you on the next video. 13. Conclusion: This concludes our class on web components. For your class project, I encourage you to create your own fully functioning web component. For inspiration, you can take what we've done so far in this class with the e-commerce example and add additional functionality such as product filtering or variance selection. As always, if you have any questions or concerns, leave a comment in the discussion box below, and I'll do my best to point you in the right direction. Thanks as always for watching and I hope to see you again on some of my other courses.