|
|
<!doctype html> |
|
|
<html> |
|
|
<head> |
|
|
<meta charset="utf-8"> |
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"> |
|
|
<title>My Learning Website</title> |
|
|
<link href="/styles/styles.css" rel="stylesheet" type="text/css"> |
|
|
<link href="/webdevelopment/styles/styles.css" rel="stylesheet" type="text/css"> |
|
|
<!-- html5 shim and Respond.js for IE8 support of html5 elements and media queries --> |
|
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// --> |
|
|
<!--[if lt IE 9]> |
|
|
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> |
|
|
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> |
|
|
<![endif]--> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div class="banner"> |
|
|
<h1 class="courselink">JavaScript Essential Training</h1> |
|
|
<h2 class="lecturer">LinkedIn Learning : Morten Rand-Hendriksen</h2> |
|
|
<h2 class="episodetitle">DOM</h2> |
|
|
</div> |
|
|
|
|
|
<article> |
|
|
<h2 class="sectiontitle">The Document Object Model</h2> |
|
|
<p>When you open a web page in a browser, the browser creates a hierarchical structure for that page which we call the Document Object Model (or DOM). For example, let's consider this paragraph which is a <p> element in html. Along with a number of other elements, it is inside an <article> element which is inside an <html> element. This <html> element is the root of the DOM, so it is not, itself, inside another element but it contains all of the other elements in this page. If we were to draw out a tree structure for this page, the <html> element would be at the top and below that we would have two elements, <head> and <body>. Below the body, we would have some <h1> and <h2> elements, the <article> element and a <div> at the bottom of the page that contains the buttons.</p> |
|
|
<p>As you can see, the levels tend to get more complicated as we move down, <html> contains two elements (one of which is <body>), <body> contains 5 elements (including <article>) and because most of the pages on this site are very similar, you will find that this structure applies to most of them.</p> |
|
|
<p>Since <article> contains the content for the notes on this particular video, the number of elements it contains will vary wildly between pages, but there will still be a similar structure. In general, that means that the first element is an <h2> element containing the name of the first part of this chapter and there will be <h2> elements for each of these sections.</p> |
|
|
<p>After that first <h2> element, the next element will be a <p> element in most cases and after that, there will be appropriate elements as required for the content I want to add to this page. This will include more <p> elements (usually), some <pre> elements which I use for snippets of code and so on and perhaps some <h3> elements. These are the block elements I tend to use for these pages but there may be some inline elements, particularly <a> elements.</p> |
|
|
<p>Beneath the <article< element, we have all of our <p> and <pre> elements and in some of these we will have <a> elements.</p> |
|
|
<p>So, we could take any element in the page and trace it right back to the <html> element. We can also identify which elements are its siblings, that is, which elements would also appear as direct descendants of the <article> element.</p> |
|
|
<p>Let's just take a few moments to consider some vocabulary which will be useful in describing the DOM. Where an element is nested inside another element, as with the <body> element which is nested inside the <html> element, we call the outer element the parent and the inner element the child. It is important to remember that we are thinking of these elements as being entire entities so the <body> element encompasses all of the HTML elements inside it. I mentioned earlier that for this page, <body> contains 5 elements, all of which is a child element of <body>. So, just like with people, an HTML element can be a child of one element but a parent of another. All of the elements that it is a parent to are referred to as siblings or sibling elements and so there are 5 siblings inside the <body> element including <article>.</p> |
|
|
<p><html> is not the parent of <article> but it is an ancestor and a <article> is a descendant of <html> and so you could draw out the whole DOM like a family tree. You might wonder why this is important for JavaScript and the reason is simply that we saw in the previous chapter how we were able to generate the <body> of our HTML document using JavaScript. We can also use JavaScript to generate a single element and the DOM tells us where this needs to be added in the document.</p> |
|
|
<p>Remember that in our JavaScript, we were able to manipulate the innerHTML property of the <body>. We didn't really do much manipulation but it would be easy enough to add an element to the start or the end of a <body> although perhaps more complex to add it elsewhere. We can also use the innerHTML property to get and manipulate the HTML for any element in the page and how we will do that is something we will cover in the rest of this chapter.</p> |
|
|
<h2 class="sectiontitle">Access Elements with querySelector Methods</h2> |
|
|
<p>The HTML for this section is a little bit more complex than we have seen previously in order to give us more to work with. The JavaScript isn't too different to the recent versions. We have a template literal to generate the HTML for our web page (that is, the HTML that displays the properties of a backpack object, in this case there is additional HTML that is not being generated by the JavaScript.</p> |
|
|
<p>The JavaScript does introduce a couple of new things, one of which is an image file although this is really just generating a bi of HTML to display the image in the same way that we are generating other HTML elements. The only thing that is entirely new is the line</p> |
|
|
<pre class="inset">const main = document.querySelector(".maincontent");</pre> |
|
|
<p>We are using this to grab the HTML element using it's class, .maincontent and this is the <element> which is initially empty. At the end of the JavaScript file, we will add the generated HTML to this element so this is not very different from the way in which we previously added to the <body> element. The difference is in the way that we are using the querySelector to grab the element and that's what we will be looking at now. The code for both main.html and script.js can be found in <a href="codesamples/chapter5/sample1.html" target="_blank">sample1.html</a> but I haven't included Backpack.js which is the same as an earlier version.</p> |
|
|
<p>If we want to work with the DOM, we need to locate the element from the DOM that we want to work with and we can do that using either querySelector (see the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector">MND Web Docs</a> for more info) or querySelectorAll (see the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll">MDN Web Docs</a> for more info).</p> |
|
|
<p>These both use CSS style selectors to target an element in much the same way that a you might target an element in order to apply a CSS style. In the example shown above, we used a class selector, .maincontent. You may have noticed that the selector is placed inside double quotes so we can insert a proper string here.</p> |
|
|
<p>The query selector returns a DOM object and this is a JavaScript object so we can work with it as needed.</p> |
|
|
<p>We can use the querySelector element to target any element just as we can target any element with CSS and the same provisos apply so, for instance, we would probably want to design our HTML to make it easier to manipulate with JavaScript and this is similar to designing HTML to make it easier to work with CSS. To give some examples from this page, I have classes for the name of the course at the top of the page (this often is a link to the course in LinkedIn so the class name is courselink), the lecturer's name (lecturer), the chapter name (episodetitle), the section headings (sectiontitle) and the code samples and other snippets (inset). This allows me to apply styles specific to each type of element and gives the whole site a more consistent look.</p> |
|
|
<p>Similarly, we might use class names to group together certain types of elements if we want to perform some operation on them using JavaScript.</p> |
|
|
<p>However, we should remember that we can use any selector including a type selector or an Advanced Selector (for more info on these, see the page in my CSS Essential Training Notes entitled <a href="/webdevelopment/cssessentialtraining/selectors.html" target="_blank">Advanced Selector</a> (this opens in a new tab).</p> |
|
|
<p>To see an example of this, in the HTML for this exercise, there is an unordered list with 7 <li> elements. Let's say that we want to target the last of these items. We can target this with an advanced selector as follows.</p> |
|
|
<pre class="inset">document.querySelector("main li:last-child");</pre> |
|
|
<p>This targets the last <li> element that is inside <main> and it is recognisable as a selector we saw in the CSS Essential Training course. So, we are using, in essence, a CSS selector to target the HTML element in JavaScript.</p> |
|
|
<p>With querySelector, we are only selecting a single element and that is the first element that matches the selector. In some cases, only one element will match (for instance, there is only one element that is the last <li> element in <main>), but in many cases, this may not be the case. For example, you might have several <div> elements that each contain an unordered list. In that case, if we used a selector such as</p> |
|
|
<pre class="inset">document.querySelector("div li:last-child");</pre> |
|
|
<p>there would be a number of elements that match the selector, but querySelector will only the return the first. If we want all possible matches to be returned, we have to use querySelectorAll instead, like this.</p> |
|
|
<pre class="inset">document.querySelectorAll("main li");</pre> |
|
|
<p>This returns a node list, which is similar to an array, and which contains all of the matched elements. In JavaScript, we could iterate through the elements of this list using, for example, a foreach loop (we will encounter these later in this course). As a taster of this, let's consider an example where we want to iterate through this list of <li> elements and set the background colour for each of them to red. We can do that with</p> |
|
|
<pre class="inset">document.querySelectorAll("main li").forEach(item => item.style.background="red");</pre> |
|
|
<p>The main point at this stage is to be aware that we can get a list containing a number of elements that match a given selector so we won't worry about the syntax of foreach for the time being.</p> |
|
|
<p>Before we move on, it would probably be worth playing around with the querySelector and querySelectorAll methods in the console to get some practice and to ensure you understand how they work. You may notice that may of the elements in the HTML documents have class names which make them easy to target.</p> |
|
|
<p>Let's say that we want to set the background colour for the line that displays the Left strap length to green. This has a class of backpack__strap so we can target it with</p> |
|
|
<pre class="inset">document.querySelector(".backpack__strap");</pre> |
|
|
<p>This returns the element as an object so we can use a method on this using dot notation.</p> |
|
|
<pre class="inset">document.querySelector(".backpack__strap").style.background="green";</pre> |
|
|
<p>The result is that the background colour for this element only changes to green. Bear in mind the fact that there are two elements with the class name backpack__strap, the left strap length and the right strap length. We only wanted to target the first of these, so this means using querySelector since this returns the first matched element and therefore that works out well enough.</p> |
|
|
<p>It would be much harder to do something similar for the right strap length, but I would guess that this would involve using querySelectorAll to return a node list and then access the element from that list.</p> |
|
|
<p>As another example, let's say that I want to change the image. This means changing its src value so we can use querySelector as follows.</p> |
|
|
<pre class="inset">document.querySelector("img").src="https://i2-prod.liverpoolecho.co.uk/incoming/article8646667.ece/ALTERNATES/s1227b/GL348916.jpg"</pre> |
|
|
<p>As there is only one image in the HTML document, we can just use the img type selector to target it and in this example, I have just used the url of an image I found with a quick Google search to set the source value (as a matter of interest, it is an old picture of Kevin Keegan from around the mid seventies).</p> |
|
|
<h2 class="sectiontitle">Access Elements Using Older Methods</h2> |
|
|
<p>There are also a couple of older methods for targetting elements in the DOM and these are getElementsByClassName() - see the documentation <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName">here</a> and getElementById() - see the documentation <a href="https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById">here</a>. You may recall that an id must be unique whereas a class name can be used multiple times in the same dicument. This is reflected in these methods in two ways, the most obvious being that we have Elements (plural) in the first method name but the singular Element in the second. The first method returns a node list of all elements that match a given class name and the second method returns a single element.</p> |
|
|
<p>There is probably no good reason to use these methods over querySelector and querySelectorAll since these newer methods were created in part to overcome the limitations of the older methods, but they do still work, you can use them if you want to and you will see them in older code and some tutorials.</p> |
|
|
<p>In <a href="codesamples/chapter5/sample2.html" target="_blank">sample2.html</a>, we have the script.js file for exercise 05_03. This is similar to the previous version with the notable difference being that each of the additional <li> elements now has an additional class name, packprop, attached. The fact that each property has this class name as well as a class name that is unique to the property (there are two proprties, left and right, for the straps so these both have the class name backpack__strap but otherwise, there is a one to one mapping between a property class name and the property) means that we can either target properties individually using the unique property name or collectively using the class name packprop.</p> |
|
|
<p>Using the exercise files, we will hsow some examples of this in the console. For example, if we type</p> |
|
|
<pre class="inset">document.getElementsByClassName("packprop");</pre> |
|
|
<p>the browser returns a list of the elements matching that query. This is similar to the node list we get with</p> |
|
|
<pre class="inset">document.querySelectorAll(".packprop");</pre> |
|
|
<p>We can do the same things with either of these lists such as iterate through each item and so on so the two methods are equivalent to each othe, querySelector is just a newer method. Notice that with querySelector and querySelectorAll, the class name in the query begings with a dot.</p> |
|
|
<p>We can combine classes just as we could with a CSS selector, so let's say that we want to target the element that has the class names packprop and backpack__color. With getElementsByClassName, we would use</p> |
|
|
<pre class="inset">document.getElementsByClassName("packprop backpack__color")</pre> |
|
|
<p>Of course, since only one element has the class name backpack__color, the other class name is superfluous here, but the important thing is that we can use multiple class names to select an element.</p> |
|
|
<p>In the sampe code, we also have an element with an id of everyday. That is the <article> element that contains the image, the backpack name and the properties. The element also has a class name which is backpack and we can use either to select this element. Let's see how this works with the id.</p> |
|
|
<pre class="inset">document.getElementById("everyday")</pre> |
|
|
<p>This returns a single object (remember that an id must be unique within a document so there is only one element that can be matched. Compare this to the querySelector.</p> |
|
|
<pre class="inset">document.querySelector("#everyday")</pre> |
|
|
<p>Again, this does pretty much the same thing, but these two methods are not really equivalent to each other in that querySelector matches the first element that matches its query. In this example, because we are using an id, there can only be one element that matches. However, we can use querySelector (as we saw previously) with a query that matches more than one element, but it still only returns the first match.</p> |
|
|
<p>Again, notice that the querySelector used a # in the query and this leads to a question which you may want to mull over. <strong>Why do we need to use a . or a # with querySelector or querySelectorAll but not with getElementById or getElementsByClassName?</strong></p> |
|
|
<p>The answer should be fairly obvious. If we take an id for example, getElementById only works with an id so we don't need to tell the browser that that's what it is. On the other hand querySelector works with any type of selector, including, class, id or type, so in this case, we do need to tell the browser what type of selector this is.</p> |
|
|
<p>Another major difference between getElementById/redand querySelector/querySelectorAll is that as the names suggest, getElementById/getElementByClassName only work with a single selector type (id or class name). However, both querySelector and querySelectorAll can take any CSS selector as an argument so it is not restricted to a single selector type and it can also combine different selector types.</p> |
|
|
<p>If you are inspecting some older code, you may see that ids are used much more commonly than they tend to bre used today (personally I tend not to use them at all). This was done so that it was easier to target elements for JavaScript using getElementById. The introduction of querySelector and querySelectorAll largely makes this redundant and this a major reason for them reflecting the older getElementById and getElementsByClassBane methods.</p> |
|
|
<h2 class="sectiontitle">Practice: Find an Element</h2> |
|
|
<p>Locating or targetting an element in the DOM tree is a very common and basic task in JavaScript. The files for this practice exercise includes an HTML file which is quite long and contains a rich DOM with plenty of elements allowing practice in using querySelector and querySelectorAll. There is no direction given so I will create my own list of exercises which I will them try to implement in the script.js file. For reference, the page we will be working on here can be found at <a href="codesamples/practice/findanelement/practice_findanelement_start.html" target="_blank">Practice: Find an Element Start</a>. Once the exercises have been completed, I will add a link to the completed file. Note that I won't be making any changes to the HTML, just the JavaScript so the only difference between this page and the finished page is going to be coming from the script.js file. You will probably want to check each line of code as you go and it's easy enough to do, depending on how you are editing the files. If you are editing them in Visual Studio Code, you can just spin up the live server every time you make a change. You could also just load the file from local storage into a browser and just refresh the browser every time you make a change.</p> |
|
|
<p>To begin with, the script.js file is empty and I will add code to this as I work through the exercises. Before I do that, it might be worth taking a quick look at the DOM tree which I will approximate in list form. The file, <a href="codesamples/chapter5/sample3.html" target="_blank">sample3.html</a> provides both a copy of the HTML file and this DOM tree.</p> |
|
|
<p>For this exercise, we will only be targeting, not manipulating elements so the general format will be to select the element and then output it to the console. We will start with a simple example where we use a class to select an element as follows.</p> |
|
|
<pre class="inset"> |
|
|
var strap1 = document.querySelector(".backpack__strap"); |
|
|
console.log(strap1);</pre> |
|
|
<p>The output we get from this is</p> |
|
|
<pre class="inset"> |
|
|
<li class="feature backpack__strap" data-side="left"> |
|
|
Left strap length: <span>26 inches</span> |
|
|
<form class="leftlength" data-children-count="1"> |
|
|
<input type="number" name="leftLength" placeholder="New left length"> |
|
|
<button>Update</button> |
|
|
</form> |
|
|
</li></pre></pre> |
|
|
<p>So here, we have used querySelector to select an item with class backpack__strap and we get the first element that matches this. If we want to get all of the elements with that class, we would use querySelectorAll.</p> |
|
|
var straps = document.querySelectorAll(".backpack__strap"); |
|
|
console.log(straps);</pre> |
|
|
<p>This time, the output is</p> |
|
|
<pre class="inset"> |
|
|
{ |
|
|
"0": {}, |
|
|
"1": {}, |
|
|
"2": {}, |
|
|
"3": {} |
|
|
}</pre> |
|
|
<p>This time, a node list has been returned with each of these representing one of the elements with class backpack_strap.</p> |
|
|
<p>Next, I want to target the last of these elements.</p> |
|
|
<pre class="inset"> |
|
|
var strap2 = straps[3]; |
|
|
console.log(strap2);</pre> |
|
|
<p>We would expect the result to be similar to the output for strap1 but with different values since it is a different element and we see</p> |
|
|
<pre class="inset"> |
|
|
<li class="feature backpack__strap" data-side="right"> |
|
|
Right strap length: <span>10 inches</span> |
|
|
<form class="rightlength" data-children-count="1"> |
|
|
<input type="number" name="rightLength" placeholder="New right length"> |
|
|
<button>Update</button> |
|
|
</form> |
|
|
</li></pre> |
|
|
<p>In this example, we made use of the fact that we had a node list containing all the elements with class backpack__strap and we simply accessed the last element in the list with straps[3]. We could also use this to access any of these elements including the first, so if you do need to access more than one element with the same class name, this is an easy way to do it. Note that I am using variable names to make the code a little easier to read, but we can access individual strap elements just by referencing the node list.</p> |
|
|
<pre class="inset"> |
|
|
console.log(straps[1]);</pre> |
|
|
<p>I won't replicate the output here but it is giving us the properties of the second strap. If you want to output to the console (or do anything else) with each element in the straps node list, you would probably use a loop for that which we will look at later in the course.</p> |
|
|
<p>Next, I want to use an anceestor selector to target the site title. This is in a div with class site-title and this is inside another div with class siteheader. It is the first div inside siteheader so we can use its type as the selector. Also, I only wanted to access it's content so I will use its innerHTML property.</p> |
|
|
<pre class="inset"> |
|
|
console.log(document.querySelector(".siteheader div").innerHTML);</pre> |
|
|
<p>We are doing everything in a single statement so we are using the querySelector to get the element, accessing its innerHTML property and outputting this to the console with console.log. As a reminder, the HTML for this div is</p> |
|
|
<pre class="inset"> |
|
|
<header class="siteheader"> |
|
|
<div class="site-title">BackpackPacker</div> |
|
|
<div class="site-description">All backpack packing, all the time.</div> |
|
|
</header></pre> |
|
|
<p>We can see that the text we want to access is BackpackPacker and this is exactly what we see displayed in the console.</p> |
|
|
<p>Let's now try to do the same thing with the site description. This has the class site-description and is the second div in the siteheader, so its a bit more difficult to access it via the same sort of type selector, but we can do that using a more advanced selector, last-child. There are only two div elements inside siteheader so we don't really need to get a node list since the querySelector above allowed us to easily access the first div and last-child allows us to access the second div since it is also the last one in siteheader.</p> |
|
|
<pre class="inset"> |
|
|
console.log(document.querySelector(".siteheader div:last-child").innerHTML);</pre> |
|
|
<p>As expected, this outputs the text of the sitedescription element. In both cases, we could have just used the class name and we could do that in an ancestor selector like this.</p> |
|
|
<pre class="inset"> |
|
|
console.log(document.querySelector(".siteheader .site-title").innerHTML); |
|
|
console.log(document.querySelector(".siteheader .site-description").innerHTML);</pre> |
|
|
<p>We can also target these elements using only the class names, site-title and site-description.</p> |
|
|
<pre class="inset"> |
|
|
console.log(document.querySelector(".site-title").innerHTML); |
|
|
console.log(document.querySelector(".site-description").innerHTML);</pre> |
|
|
<p>In all three cases, the output is exactly the same. This illustrates the point that there are usually multiple ways to target an element. However, it is also important to remember that in order to know which selector to use, you need to know how the HTML is structured.</p> |
|
|
<p>For example, the first pair of statements worked because there are two divs in the siteheader so we can access them by taking the first div in siteheader or (using last-child) the last div. If we had, let's say, 5 divs, this method isn't really suitable for accessing a single div but it will still work for the first or last div.</p> |
|
|
<p>The second method worked because we have just one element with the class siteheader and inside of that, we have only one div with the class site-title, and only one with the class site-description. Imagine we had another element with class siteheader2 which also had two divs, one with class site-title and one with class site-description. Using site-header allows us to target precisely the site-tile or site-description we want and we would be able to, similarly, target these elements in siteheader2 by using that as the ancestor selector.</p> |
|
|
<p>The third method works because we don't have any other elements with these classes so the class name itself is enough to target exactly the element we want. In this context, where a class name is unique, it is essentially an id. In such circumstances, the class name is the easiest selector to use.</p> |
|
|
<p>For comparison, recall that there are four elements with class backpack--strap. There alre also two elements with the class backpack__features (one for each backpack) and each of these contains a pair of the strap elements. This makes it more difficult to use the class names as selectors. You may have noticed that querySelectorAll returned a node list containing all of the strap elements and it doesn't care that they are in different features elements so querySelectorAll is probably a good option to use there.</p> |
|
|
<p>Staying with the strap element for a moment, note that each back__strap element contains four different elements, a span, a form (which has a classname of either leftlength or rightlength, an input and a button. Both the inout and the button are nested inside the form element.</p> |
|
|
<p>What I want to do here is to select elements from all four of these back__strap elements, but also go through the process and show what factors are being considered before deciding on the best way to select a particular element.</p> |
|
|
<p>We will start with an easy one and that is the button contained within the first strap element. This is a left strap so its form element has class leftlength and since this is both the first strap element and the first left strap, we can make use of that by just using the class selector so that can be either</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength"));</pre> |
|
|
<p>or we can skip the first class and just use</p> |
|
|
<pre class="inset">console.log(document.querySelector(".leftlength"));</pre> |
|
|
<p>Either of these methods will work and give us the same result which should look something like this</p> |
|
|
<pre class="inset"> |
|
|
<form class="leftlength" data-children-count="1"> |
|
|
<input type="number" name="leftLength" placeholder="New left length"> |
|
|
<button>Update</button> |
|
|
</form></pre> |
|
|
<p>Remember that within this element, we have an input element and a button element and these have visible properties. Let's say that we now want to check on one of these properties such as the button's text. The button is an element too, so we can use the same selector again but add the button on to it. We will use the second of our two selectors since it is a little shorter so this gives us</p> |
|
|
<pre class="inset">console.log(document.querySelector(".leftlength button").innerHTML);</pre> |
|
|
<p>As expected, this gives us the output "Update" which is the label on the button. This works perfectly well but it does seem to be a little bit long-winded. If we want to make this a little more readable, we could take a slightly different approach. Previously, we took the whole strapleft element and logged it to the console as an entity. What we will do now is to create a variable, we'll call is strapleft1, assign that entity to our variable and then use that to access its innerHTML property.</p> |
|
|
<pre class="inset"> |
|
|
var strapleft1 = document.querySelector(".leftlength button"); |
|
|
console.log(strapleft1.innerHTML);</pre> |
|
|
<p>We can also use the same variable to change the innerHTML like this</p> |
|
|
<pre class="inset">strapleft1.innerHTML = "UPDATE!";</pre> |
|
|
<p>Note, we don't need a variable to do that, we could have used it with a conventional querySelector so</p> |
|
|
<pre class="inset">document.querySelector(".leftlength button").innerHTML = "Update again!"</pre> |
|
|
<p>would work just as well.</p> |
|
|
<p>When we target the first left strap, this also happens to be the first strap so selecting the first left strap element from the first strap element was straightforward. What would happen if we used the first strap element together with the first right strap element. I don't know the answer here but I suspect that it will either return nothing since the first strap element doesn't have a right strap (this is the element for the first strap which is a left strap) or it will overlook the first strap and find the first strap that contains a right strap element. I expect it will be the former, so let's try it out.</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .rightlength"));</pre> |
|
|
<p>The output this gives us is</p> |
|
|
<pre class="inset"> |
|
|
<form class="rightlength" data-children-count="1"> |
|
|
<input type="number" name="rightLength" placeholder="New right length"> |
|
|
<button>Update</button> |
|
|
</form></pre> |
|
|
<p>This is a little bit of a surprise for me because I thought that the browser would look for the first element with class backpack__strap, look inside it for an element with class rightlength and return null because the element is not found. I guess that what actually happened is that when no element with class rightlength was found, it then looked in the next one. Another explanation, and one that actually fits in a little better with what I know of CSS is that the browser has simply looked for the first right strap which is nested inside an element with class backpack__strap.</p> |
|
|
<p>As a further experiment, I have added a second class to the last strap element and I have called this laststrap and this time I have tried to target the second right strap with</p> |
|
|
<pre class="inset">console.log(document.querySelector(".laststrap .rightlength"));</pre> |
|
|
<p>The point of doing this is that I'm not sure exactly how the selector works. It does correctly find the last strap so I guess that it doesn't really matter in which order the class names are parsed, the element it targets is the first element that belongs to both classes.</p> |
|
|
<p>That was a bit of a digression there. Let's get back on track by trying to target the second left strap element.</p> |
|
|
<p>We know that</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength"));</pre> |
|
|
<p>targets the first left strap so we know that it can be used to locate a left strap element. We also know that there are only two left straps (one for each of the twp backpack objects). In that case, we can use the last-child selector so</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength:last-child"));</pre> |
|
|
<p>It might be helpful to note, at this point, that the first backpack object has a length of 26 for both left and right straps whereas these lengths are both 10 for the second backpack object. This is important because it allows us to verify that the right element has been selected.</p> |
|
|
<p>This certainly seems to work, but let's just do a final example using a variable. We will create a variable, call it secondleft and assign the result of this querySelector statement to it.</p> |
|
|
<pre>var secondleft = document.querySelector(".backpack__strap .leftlength:last-child");</pre> |
|
|
<p>I will then try to output the length with this statement</p> |
|
|
<pre class="inset">console.log("Secondleft length is ",secondleft.span.innerHTML);</pre> |
|
|
<p>This gives an error as follows:</p> |
|
|
<pre class="inset"> |
|
|
Uncaught TypeError: secondleft.span is undefined |
|
|
<anonymous> http://osztromok.com/webdevelopment/javascriptessentialtraining2021/codesamples/practice/findanelement/script.js:38 |
|
|
script.js:38:1</pre> |
|
|
<p>I guess that the problem here is that you can't use a type in the same way as you can a property so we need to modify the assignment.</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength:last-child span"));</pre> |
|
|
<p>In theory, this should allow me to use the variable to access the property.</p> |
|
|
<pre class="inset">console.log("Secondleft length is ",secondleft.innerHTML);</pre> |
|
|
<p>This also gives an error. It looks as though the problem is with the span, in other words, we have a malformed selector. This can be verified by entering this from the console.</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength"));</pre>/pre> |
|
|
<p>This returns a left strap element as expected. We can then add the last-child.</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength:last-child"));</pre> |
|
|
<p>This also returns a left strap element.</p> |
|
|
<p>Finally, we can add the span</p> |
|
|
<pre class="inset">console.log(document.querySelector(".backpack__strap .leftlength:last-child"));</pre> |
|
|
<p>This time, the return value is undefined and the reason is actually quite clear if you refer back to the HTML. We are looking for a span inside a left strap but actually, we should be more specific and say an element with class left-strap. This is nested inside the element with class backpack__strap, but we are assuming that it is the parent element for the span whereas it is actually a sibling.</p> |
|
|
<p>To solve this problem, I decided to go back to basics in order to determine what the required selector was so in the broswer developer tools, I opened up the console. I also referred back to the DOM to try to work out how to approach this. The element that I want to target is a span which is nested inside an element with class backpack__strap. The parent element for this is a figure with class backpack__features which in turn was a parent element main (class maincontent) and this is a child element of body so that's as far back as we want to go.</p> |
|
|
<p>In the console, I typed the following.</p> |
|
|
<pre class="inset">document.querySelector("body");</pre> |
|
|
<p>Next, I typed</p> |
|
|
<pre class="inset">document.querySelector("body article");</pre> |
|
|
<p>and then</p> |
|
|
<pre class="inset">document.querySelector("body article: last-child");</pre> |
|
|
<p>Each time, I am checking that the output is what I expect it to be and by doing this, I can construct the selector one step at a time so I know that I am targeting the element that I think I am targeting. In this case, I want to be able to target the second packlack and since each pack is inside an article element, I can do that by selecting the element with class article that is the last child of body. Now that we have the right pack, I can access its backpack__strap element by adding that to the previous selector.</p> |
|
|
<pre class="inset">document.querySelector("body article:last-child backpack__strap");</pre> |
|
|
<p>Again, I checked that the correct element was returned. Our span element is a child of this element so we can just tag that onto the selector.</p> |
|
|
<pre class="inset">document.querySelector("body article:last-child .backpack__strap span");</pre> |
|
|
<p>Now that I have the correct selector to target the span I need to access, I can use a variable to store that span element like so.</p> |
|
|
<pre class="inset">var secondleft = document.querySelector("body article:last-child .backpack__strap span");</pre> |
|
|
<p>Finally, I can output this with</p> |
|
|
<pre class="inset">console.log("Label for the left strap in the second backpack is", secondleft.innerHTML);</pre> |
|
|
<p>I can also use this selector, with a slight modification, as another way to target the right strap in the second pack.</p> |
|
|
<pre class="inset">var secondleft = document.querySelector("body article:last-child .backpack__strap:last-child span");</pre> |
|
|
<p>This exercise was fairly useful for me in that it showed me that it is possible to work out what selector is needed to target a very specific element by targetting its parent or a descendant element first and then building on that until the selector targeted exactly the element that I wanted to target.</p> |
|
|
<p>In addition, it's really a useful demonstration of the fact that when you are targetting a specific element, you might be taking advantage of the fact that it is the first or last element with a particular class, but as you develop your HTML, that may change. I guess that the best way to avoid problems like these are to be aware that it can happen and also be aware of what JavaScript you have, which querySelectors you are using and then either ensure that when you update the HTML, you do so in a way that doesn't break your JavaScript queries.</p> |
|
|
<p>The completed version of the example code can be found in the page entitled <a href="../../webdevelopment/javascriptessentialtraining2021/codesamples/practice/findanelement/practice_findanelement_end.html">practice_findanelement_end.html</a>.</p> |
|
|
<h2 class="sectiontitle">Modifying Element Classes</h2> |
|
|
<p>In JavaScript, modifying an element's class or classes is quite common and can be used to change the appearance or behaviour of an element. There are two properties designed to manipulate these classes. The first of these is className which we can use to either return a list of class names associated with a particular element or we can use it to amend the class name. We can look into this by opening up the HTML file from the exercise files (05_05) and going in yo the console.</p> |
|
|
<p>We will start by using querySelector to get an element.</p> |
|
|
<pre class="inset">document.querySelector("h1");</pre> |
|
|
<p>This returns the first h1 element (actually, the output also gives us the class name). We can also get the class name like this.</p> |
|
|
<pre class="inset">document.querySelector("h1").className;</pre> |
|
|
<p>This gives us the output</p> |
|
|
<pre class="inset">"backpack__name"</pre> |
|
|
<p>To change this, we simply assign a new value to the element like this.</p> |
|
|
<pre class="inset">document.querySelector("h1").className = "pack__name";</pre> |
|
|
<p>Now, if we execute this statement</p> |
|
|
<pre class="inset">document.querySelector("h1").className;</pre> |
|
|
<p>This gives us the output</p> |
|
|
<pre class="inset">"pack__name"</pre> |
|
|
<p>So, this is a simple way to fetch or modify a class name, but it does present a couple of challenges. In this example, the element has a single class name so we are just replacing it with a new one. However, if the element had serveral class names, assigning a new class name would replace all of them.</p> |
|
|
<p>We can partially overcome this in a couple of ways. The first is that we can assign a list of classes in a single statement like this.</p> |
|
|
<pre class="inset">document.querySelector("h1").className = "backpack__name pack__name";</pre> |
|
|
<p>The drawback here is that you need to know what the class names are and this is fine in some circumstances, particularly if you are dealing with a short list of class names, but it doesn't allow you to write code that dynamically changes the class names, regardless of existing values.</p> |
|
|
<p>Howecer, we can also use this type of statement to dynamically add a class to an existing class list like this.</p> |
|
|
<pre class="inset">document.querySelector("h1").className = document.querySelector("h1").className + " new_class";</pre> |
|
|
<p>This is getting the existing list of class names, adding a new class name to it and then writing the modified list of class names back to the element so that allows us to dynamically add a new class name to an element without removing any of the existing class names.</p> |
|
|
<p>This does give us some control over the class names of our HTML elements, but it isn't particularly flexible. There is another problem which is that some JavaScript frameworks such as React use className as a replacement for class to avoid any conflicts with JavaScript so if you do use it inside a framework, the code will most likely not do what you had intended.</p> |
|
|
<p>That's the first property. The second is newer and is designed to overcome these problems and that is classList. This returns a DOM token collection of all the classes appended to an element.</p> |
|
|
<pre class="main">document.querySelector("main li:first-child").classList</pre> |
|
|
<p>The output form this is</p> |
|
|
<pre class="inset">DOMTokenList [ "packprop", "backpack__volume" ]</pre> |
|
|
<p>The main advantage here is that we can now work with each of these classes individually. We can work with this in the same way as we did with className so, for instance</p> |
|
|
<pre class="inset">document.querySelector("main li:first-child").classList = singleClass</pre> |
|
|
<p>will add the class name singleClass to the element, replacing any previous classes. Similarly, assuming we set the classes for this element back to their original values (remember, we are doing this in the console so we can reset everything just by refreshing the page) the following</p> |
|
|
<pre class="inset">document.querySelector("main li:first-child").classList = document.querySelector("main li:first-child").classList + " singleClass";</pre> |
|
|
<p>will add singleClass to the list of class names without affecting the class names already appended to the element. However, if we do that, we are not manipulating the classes individually and classList provides us with some methods to do just that. For example, if we want to add a new class, we can use the add method without worrying about whether the element already has any class names since this method does not replace the existing classes. We would do this with the statement</p> |
|
|
<pre>document.querySelector("main li:first-child").classList.add("newClass");</pre> |
|
|
<p>Note that in the console, when we execute this statement, we see that the value returned is undefined but this just means that the method isn't returning anything. We can use classList again on the element to confirm that it has been added.</p> |
|
|
<p>Simlarly, we can remove any existing class name with the remove method.</p> |
|
|
<pre class="inset">document.querySelector("main li:first-child").classList.remove("newClass");</pre> |
|
|
<p>This will remove the specified class but will not affect any other classes.</p> |
|
|
<p>We can use the add and remove methods to manipulate an item in the class list, but there is also a toggle method if we have a situation where we want to add a class name if it isn't already appended to an element and remove it if it is.</p> |
|
|
<pre class="inset">document.querySelector("main li:first-child").classList.toggle("newClass");</pre> |
|
|
<p>This will return a value of true or false. If the class name didn't exist previously, it is added to the class list and true is returned. If it did already exist, it is removed from the class list and false is returned.</p> |
|
|
<p>Finally, classList has a replace method which takes two class names as arguments. For example</p> |
|
|
<pre class="inset">document.querySelector("main li:first-child").classList.toggle("newClass");</pre> |
|
|
<p>will replace the class neeClass with the class oldClass. That is assuming that the element has a class called newClass. If it does, you will see true being returned. However, if the class name newClass is not in the class list, the command will do nothing and will return a false value.</p> |
|
|
<p>As a general rule, you should use classList to add, remove, toggle or replace a class name. The only advantage in using className is that it returns a string cotaining all the class names seperated by a space so if you do want all of the class names in a string, it can be useful. Note that it is because className returns a string with the class names that we see this list when we execute any command that manipulates the list.</p> |
|
|
<h2 class="sectiontitle">Attributes</h2> |
|
|
<p>Any element has a list of attributes which are stored in the attributes property and we can access that list.</p> |
|
|
<pre class="inset">document.querySelector(img).attributes;</pre> |
|
|
<p>This gives us the output</p> |
|
|
<pre class="inset"> |
|
|
NamedNodeMap [ src="../assets/images/everyday.svg", alt="" ] |
|
|
|
|
|
0: src="../assets/images/everyday.svg" |
|
|
|
|
|
1: alt="" |
|
|
|
|
|
alt: alt="" |
|
|
|
|
|
length: 2 |
|
|
|
|
|
src: src="../assets/images/everyday.svg" |
|
|
|
|
|
<prototype>: NamedNodeMapPrototype { getNamedItem: getNamedItem(), setNamedItem: setNamedItem(), removeNamedItem: removeNamedItem(), … }</pre> |
|
|
<p>Note that the list of properties we are seeing returned here is a named node map so we can't treat it as an array. It is a map, so a list of key value pairs so we can manipulate the key, the value or both.</p> |
|
|
<p>In the example shown above, we used am image element because this has attributes that we can see in the list and these include alt (which in this case has an empty string value and src which hold the source value.</p> |
|
|
<p>There are methods we can use to manipulate these attributes. We can use the hasAttribute method to determine whether an element has a specified element and this will return either true or false, depending on whether it has the attrbitute or not. For instance, let's say that we want to know whether our image element has an alt value, we can test that with</p> |
|
|
<pre class="inset">document.querySelector("img").hasAttribute("alt");</pre> |
|
|
<p>which will return true in this case.</p> |
|
|
<p>If we want to get the value of an attribute, we can use the getAttribute method. For example, we might want to get the value for the source attribute so we can do that with</p> |
|
|
<pre class="inset">document.querySelector("img").getAttribute("src");</pre> |
|
|
<p>and that will return the image's URL. Note, that if we get the alt attrbiute using the same method, it may look as though nothing is being returned, but in fact we have a pair of double quotes returned. This is because the attribute doesn't have a null value or no value, it has a string value and that string just happens to be empty.</p> |
|
|
<p>If we have a get attrbiute you might expect (particularly if you are familiar with getter and setter methods in an object-oriented programming language, that we will also have a set attrbiute method and we do indeed. So, let's say that we want to change the value of our source attribute so that a different image is displayed, we can do that with a command like this.</p> |
|
|
<pre class="inset">document.querySelector("img").setAttribute("src", "images/DSC_0371-1-3.jpg");</pre> |
|
|
<p>Notice that the setAttribute takes two arguments, the name of the attrbiute (in this case "src") and the value we want to set this to and these are comma separated. In this case, I have simply created a folder called images in the exercise folder (05_06) for this video and placed an image inside it so that I can easily access it from either the JavaScript code in VS Code or via the console and either does the same thing although setting it via the console means that we can revert to the original image by refreshing the page. The important point here is to ensure that the URL is valid and points to the correct image otherwose you will either see the wrong image being loaded or a blank space if an image can't be located.</p> |
|
|
<img src="/images/" alt=""> |
|
|
<p>Anything inside a tag is an attribute so this means src and alt when we are talking about an image tag. Some attributes can be applied to any element and an example of this would be class. One of the implications of this is that we can use these methods to manipulate class names although this is not as flexible as using the classList methods. As an example, we might add a class called image_class to the image element like this.</p> |
|
|
<pre class="inset">document.querySelector("img").setAttribute("class", "image_class");</pre> |
|
|
<p>Just as a quick summary, there are four methods we can use with attributes and these are.</p> |
|
|
<pre class="inset"> |
|
|
• hasAttribute |
|
|
• getAttribute |
|
|
• setAttribute |
|
|
• removeAttribute</pre> |
|
|
<p>We can use any of these methods with the class attribute but remember that when we retreive a list of attributes</p> |
|
|
<pre class="inset">document.querySelector("img").attributes;</pre> |
|
|
<p>we get a NamedNodeMap which is a series of key value pairs. Any attribute, including class, is a key value pair so we don't have the same type of fine control we have with classList. The value is a single string so if the element has several appended class names so much like className, if we want to, for example, add a new class to an element that already has one or more classes, we would do that with setAttribute. The parameter for this would be a string cotaining the entire list of class names, including the new name.</p> |
|
|
<p>Let's say that our image element has a class called oldClass and we want to add another called newClass. If we do that with setAttribute, the command would look like this.</p> |
|
|
<pre class="inset">document.querySelector("img").setAttribute("class", "oldClass newClass");</pre> |
|
|
<p>The key point is that although manipulating the class name as an attribute is less flexible than the classList methods, it is possible to manipulate them in that way. There are also a number of other attributes you can manipulate in this way including style.</p> |
|
|
<h2 class="sectiontitle">Inline Style</h2> |
|
|
<p>I mentioned in the previous section that style is also an attribute of an element and we will look at that in more depth here. We'll start by accessing the style property of the title (this is in the files from the 05_07 folder in the exercise files). The title has a class called site-title which we will use as our selector.</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style;</pre> |
|
|
<p>This returns a map of CSS properties we can manipulate using the style property. To start with, let's check on the colour property.</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style.color;</pre> |
|
|
<p>This returns black but we change it to, let's say, purple with a command such as</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style.color = "rebeccapurple";</pre> |
|
|
<p>As you might expect, this causes the title to change colour to purple. If we use the command</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style;</pre> |
|
|
<p>and then look at the CSS2 properties map, if we scroll down far enough we will see that there is a property called CSS text and this is, in essence, the string we have created to hold styles applied via the style attribute. Now that we have changed the text colour above, this now has a value as shown below.</p> |
|
|
<pre class=inset>cssText: "color: rebeccapurple;"</pre> |
|
|
<p>This is the recommended way to handle inline styles because of the fact that it allows you to target only the property you want to change, but you can also use setAttribute to set a value for the inline styles. Let's assume that in addition to making the text purple, we also set the background colour to yellow with</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style.background = "yellow";</pre> |
|
|
<p>Now, we have applied two different styles, colour and background colour, but adding the background colour didn't affect any other styles, so the font colour is still purple. Let's say that we now want to set the background colour to pink using setAttribute. We can do that with</p> |
|
|
<pre class="inset">document.querySelector(".site-title").setAttribute("style", "background-color: pink");</pre> |
|
|
<p>This sets the background colour to pink but what we are actually doing is setting the css style to have this style only. Any previously applied styles will be removed, in this case turning the font-colour back to black. If we execute the command</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style</pre> |
|
|
<p>again and scroll down to check the value of cssText, it now has the value</p> |
|
|
<pre class="inset">cssText: "background-color: pink;"</pre> |
|
|
<p>We can also use the cssText property to set this value directly</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style.cssText="background-color: blue;";</pre> |
|
|
<p>If we use this method, we would need to set the property of cssText to a value that included all of the styles we want to apply. However, there is no real advantage to using this method, so setting the properties individually is generally seen as the better option since it gives you more control and also means that you don't have to worry about what inline styles are already applied.</p> |
|
|
<p>It may also be worth just mentioning that the sertAttribute method can remove some inline styles but it won't indirectly affect any external CSS. What this means is that if you had a CSS rule that set the background colour to green, for example, and then you use the setAttribute method to set the font-colour to yellow</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style.cssText="color: yellow;";</pre> |
|
|
<p>This will not affect the background colour because it is not an inline style. As far as I am aware, it is possible that if an external style exists to set the font colour to something else, this type of statement is not guaranteed to work because an external CSS rule may use a more specific CSS selector, but if it doesn't, using an inline style in any of the ways described here will override the external rule.</p> |
|
|
<p>One further point to make is that in CSS rules very often have hyphenated names such as background-color and this is fine in CSS. However, property names cannot be hyphenated so they use camel case instead. As an example of this, we could set the background colour to purple, for instance, like this.</p> |
|
|
<pre class="inset">document.querySelector(".site-title").style.backgroundColor = "rebeccapurple";</pre> |
|
|
<p>It's also worth remembering that when you call up the style property for an element, the CSS2Properties map lists all of the possible properties so if you are unsure of what the exact name is for a particular style, you can scroll down this list to find that style.</p> |
|
|
<h2 class="sectiontitle">Add DOM Elements</h2> |
|
|
<p>In many of the examples we have seen so far, we have created one or more backpack objects and some HTML elements in our JavaScript file and then added this to the index.html file with a command like this.</p> |
|
|
<pre class="inset">main.innerHTML = content;</pre> |
|
|
<p>Let's quickly break this down. Firstly, main is a variable created with this line of code.</p> |
|
|
<pre xlass="inset">const main = document.querySelector(".maincontent");</pre> |
|
|
<p>So, main represents the HTML element with class name maincontent. The HTML file contains three divs inside body and these are header, main and footer and as you might expect, the main div is the HTML element with the class name maincontent.</p> |
|
|
<p>The code in the JavaScript file builds the HTML that we want to display in the main section of index.html and then the assignment statement assigns that HTML code to the innerHTML property of main and as a result we get this HTML displayed in the browser.</p> |
|
|
<p>There are a couple of drawbacks to this. Since we are using a straightforward assignment to set the value of the innerHTML, any code that was already there is overwritten. In addition, since we are adding the HTML to an element in the index.html file, that element has to exist before we can do that.</p> |
|
|
<p>We can solve both problems by creating a new DOM element and adding it exactly where we want it in the HTML. Manipulating DOM elements in this way is very important in JavaScript so there are plenty of tools to help us do that.</p> |
|
|
<p>We can use the createElement method to create a new DOM element like this.</p> |
|
|
<pre class="inset">const newArticle = document.createElement("article");</pre> |
|
|
<p>So, this is creating a new article element referenced by newArticle. We are doing this in the script.js file so I have placed a copy of this file's starting point in <a href="codesamples/chapter5/sample4.html" target="_blank">sample4.html</a>. The line to create this new const value goes after the line creating the const, main.</p> |
|
|
<p>In that file, we already have a line</p> |
|
|
<pre class="inset"><article class="backpack" id="everyday"></pre> |
|
|
<p>What we are doing here, in essence, is replacing this line so that rather than simply including article in the HTML we create here, we will be creating it with the createElement method, but otherwise, it should be the same. This means that we need to add the class and id values and we will do that with classList for the class and setAttribute for the id.</p> |
|
|
<pre class="inset"> |
|
|
newArticle.classList.add("backpack"); |
|
|
newArticle.setAttribute("id", "everyday");</pre> |
|
|
<p>So, now we have our string literal called content which we had previously used to set the value for the innerHTML of the main element of index.html. We removed the article tag and then created a new article element (newArticle) which we have given a class name and an id, but it doesn't have any content. The content string literal is then added to our newArticle in the same way that it had previously been added to main.</p> |
|
|
<pre class="inset">newArticle.innerHTML = content;</pre> |
|
|
<p>This beings up to more or less the point we were at with previous exercises except that where we had added content directly to the HTML in index.html. This time, it has been added to the element, newArticle, which has not yet been added to the HTML file, so we need some way to add it. One way to do that is with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/append">Element.append()</a> method. It is used like this.</p> |
|
|
<pre class="inset">main.append(newArticle);</pre> |
|
|
<p>What this is actually doing is appending newArticle as the last child element of main and remember that main in this instance is a reference to main in index.html. That is, we created a variable in JavaScript called main which points to the element in index.html with class name maincontent which is the main element. We didn't have to call it main, but using the same name as the HTML element helps to make things a little clearer.</p> |
|
|
<p>The result is that our index.html file includes all of the HTML elements found in the HTML plus the new element we created in the JavaScript so we are not overwriting the elements that are already there.</p> |
|
|
<p>The append method can add a single element as it did here or we can use it to add several comma separated elements or a free text string and the element or elements are added to the end of the HTML element we are appending them to. We can also add them to the start of the element with the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/prepend">Element.prepend</a> method.</p> |
|
|
<p>Other methods we can use to add an element inlcude</p> |
|
|
<pre class="inset"> |
|
|
• <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild">Node.appendChild()</a> - this is useful if you need to move an element from one location to another in the DOM or you need the browser to return the appended object so that you can do some additional work since it returns this element to you. |
|
|
• <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild">Node.replaceChild()</a> - which replaces the child element of a parent. |
|
|
• <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore">Node.insertBefore()</a> - which inserts an element before the parent element. |
|
|
• <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement">Element.insertAdjacentElement()</a> - which inserts an element after the parent element.</pre> |
|
|
<p>The last two in particular are really useful because they allow you to inject a new HTML element into the DOM exactly where you want to add it.</p> |
|
|
<p>For reference, the final version of our JavaScript file can be found in <a href="codesamples/chapter5/sample5.html" target="_blank">sample5.html</a>.</p> |
|
|
<h2 class="sectiontitle">Challenge: Add a New Element</h2> |
|
|
<p>The challenge, here, is to create a new HTML element that will be a navigation bar which we will then insert into an HTML file. We will create the nav bar as a nav element using JavaScript and do some basic styling with it using a CSS file so that the navigation items are displayed horizontally. A step by step description of this is</p> |
|
|
<pre class="inset"> |
|
|
• Step 1 - create a new element in JS to hold the navigation menu. |
|
|
• Step 2 - create an unordered list with at least 5 links. |
|
|
• Step 3 - add the new navigation elements to the DOM directly after the header.</pre> |
|
|
<p>For reference, the starting point for this exercies is <a href="codesamples/practice/addanelement/fakeindexstart.html" target="_blank">fakeindexstart.html</a>.</p> |
|
|
<p>The JavaScript for the completed challenge is shown below.</p> |
|
|
<pre class="inset"> |
|
|
1 /** |
|
|
2 * Note: JavaScript file for the Add an Element challenge in chapter 5 - the DOM |
|
|
3 |
|
|
4 */ |
|
|
5 |
|
|
6 const content = ` |
|
|
7 <nav class="horizontalmenu" |
|
|
8 <ul> |
|
|
9 <li><a href="/webdevelopment/webdevelopment.html">Web Development</a></li> |
|
|
10 <li><a href="/linux/linux.html">Linux</a></li> |
|
|
11 <li><a href="/photography/photography.html">Photography</a></li> |
|
|
12 <li><a href="/programming/programming.html">Programming</a></li> |
|
|
13 <li><a href="/hungary/hungary.html">Hungary</a></li> |
|
|
14 <li><a href="/others/others.html">Others</a></li> |
|
|
15 </ul> |
|
|
16 </nav> |
|
|
17 `; |
|
|
18 |
|
|
19 const header = document.querySelector(".header"); |
|
|
20 |
|
|
21 const nav = document.createElement("nav"); |
|
|
22 nav.innerHTML = content; |
|
|
23 |
|
|
24 header.append(nav);</pre> |
|
|
<p>On lines 6 to 17, we are creating a constant called content and initialising it with a string literal that will later be added to our new HTML element. This is fairly straightforward and is just a string of HTML code with an element called that contains an unordered list of 6 links.</p> |
|
|
<p>On line 19, we have created a constant called header. This has been initialised with a query selector which uses a class selector which is also header, so header in our JavaScript can be used to reference that element (header) in the HTML. We will use that to position our nav element.</p> |
|
|
<p>On line 21, we are creating a new DOM element called nav. Note that at this point, it is a DOM element so essentially its an HTML element without any HTML. On line 22, we add the HTML from our string literal to nav by using its innerHTML to hold content.</p> |
|
|
<p>On line 24, we add the DOM element to the index.html file by using the Element.append method. In this case, element is the element in the DOM (in other words, the element in index.html file) that header was pointing to so we are using the append method with header. In this context, header is not a class, it is a JavaScript variable that points to the element in index.html that has class header. It may be possible to confuse the two and if you find that you are confused, remember that we don't have to call this variable header. We used the same name to make it clear that this is what it is pointing to but if it makes things clearer for you, you could give it a different name such as js_header to make it clear that this is a JavaScript variable.</p> |
|
|
<p>The final version of the html file showing the horizontal navigation menu can be seen as <a href="codesamples/practice/addanelement/fakeindexend.html">fakeindexend.html</a>.</p> |
|
|
</article> |
|
|
|
|
|
<div class="btngroup"> |
|
|
<button class="button" onclick="window.location.href='stringoutput.html';"> |
|
|
Previous Chapter - Sidebar: String Output |
|
|
</button> |
|
|
<button class="button" onclick="window.location.href='variables.html';"> |
|
|
Next Chapter - Sidebar: Variables and Data Types |
|
|
</button> |
|
|
<button class="button" onclick="window.location.href='javascriptessentialtraining2021.html'"> |
|
|
Course Contents |
|
|
</button> |
|
|
<button class="button" onclick="window.location.href='/webdevelopment/webdevelopment.html'"> |
|
|
Web Development Page |
|
|
</button> |
|
|
<button class="button" onclick="window.location.href='/index.html'"> |
|
|
Home |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
</body> |
|
|
</html> |