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.