Tagged "javascript"
What are common mobile usability issues and how can you solve them?
In this article, I'm going to show you some common mobile usability issues reported by Google, and show you how to fix them using basic css.
Mobile usability issues in Google Search Console
A few days ago I received this rather alarming email from Google.
I was surprised to see this, because, although my blog isn't the most beautiful blog on the web, I pride myself on the clarity for reading on any device. So I logged into Google Search Console and found the "Mobile Usability" link on the left side to get more information.
Search console gave me the same three issues.
- Viewport not set
- Text too small to read
- Clickable elements too close together
Search console said they were all coming from https://evanxmerz.com/soundsynthjava/Sound_Synth_Java.html. This url points at the file for my ebook Sound Synthesis in Java. This ebook was written using markup compatible with early generations of Kindle devices, so it's no surprise that Google doesn't like it.
When I opened the book on a simulated mobile device, I could see that the reported mobile usability issues were apparent.
Let's go through each of the issues and fix them one by one. This will require some elementary CSS and HTML, and I hope it shows how even basic coding skills can be valuable.
Viewport not set
Viewport is a meta tag that tells mobile devices how to interpret the page on a device. We need to provide it to give web browsers a basis for showing a beautiful page to viewers. In practical terms, this means that without a viewport meta tag, the fonts on the page will look very small to viewers on mobile devices because their browsers don't know how to scale the page.
To solve this issue, add this line to the html head section.
<meta name="viewport" content="width=device-width, initial-scale=1">
This may also solve the remaining issues, but we'll add some css for them just to be sure.
Add a CSS file
The following two fixes require us to use some css, so we will need to add a CSS file to our webpage.
Early kindles didn't support much css, so it was better to just format your books with vanilla html and let the Kindle apply formatting. That limitation doesn't apply to the web, though, so I created a file called styles.css in the same directory as my html file, and added this line within the head section of my html page. This tells the page to pull style information from the file called "styles.css".
<link rel="stylesheet" href="styles.css">
Next we need to add some style rules to fix the remaining mobile usability issues.
Text too small to read
Making the font larger is easy. We could do it for only mobile devices, but I think increasing the font size slightly will help all readers. The default font size is "1rem" so let's bump it up to 1.1 by adding the following code to our css file.
body {
font-size: 1.1rem;
}
Clickable elements too close together
The only clickable elements in this document are links. So this means that the text is too small, and the lines are too close together. The previous two fixes might also address this issue, but just in case they don't, let's make the lines taller by adding another line to our body css.
body {
font-size: 1.1rem;
line-height: 1.5;
}
The default line-height is 1. By moving it to 1.5, we are making lines 50% taller. This should make it easier for users to click the correct link.
Putting it all together
In addition to solving the mobile usability issues from Google, I wanted to ensure a good reading experience on all devices, so here's my final set of rules for the body tag in my css file.
At the bottom I added rules for max-width, margin, and padding. The max-width rule is so that readers with wide monitors don't end up with lines of text going all the way across their screens. The margin rule is an old-fashioned way of horizontally centering an element. The padding rule tells the browser to leave a little space above, below, and beside the body element.
body {
font-size: 1.1rem;
line-height: 1.5;
max-width: 800px;
margin: 0 auto;
padding: 10px;
}
When I opened up the result in a simulated mobile device, I could see that the issues were fixed.
How to make Dropbox ignore the node_modules folder
In this article I'm going to show you how to get Dropbox to ignore the node_modules folder.
Why code in a Dropbox folder?
I recently bought a new computer and experienced the pain of having to sync around 300Gb of files from one Windows machine to another. This was primarily due to the in-progress music projects on my computer.
So I decided that I would just work within my Dropbox folder on my new computer. In the past, I've spoken to colleagues who did this. They were mostly artists, and didn't use git.
What I didn't consider at the time, was that this would mean there would be multiple syncing mechanisms for my code. I would be sending the files both to Dropbox and Git.
Even worse, Dropbox locks files while it is working. So if you are running something like "npm install" or "npm update", those processes can fail because they can't work on a file at the same time as Dropbox.
I began experiencing these problems almost immediately, so I either had to find a way to ignore the node_modules folder in Dropbox or stop working on code within the Dropbox folder.
Dropbox ignore syntax
Fortunately, Dropbox gave us a way to ignore files or folders a few years ago. So for Windows users, the following command will ignore your node_modules folder. Please note that this needs to be done for every project, and that the command needs to be completed with your actual directory structure.
Set-Content -Path 'C:\Users\yourname\Dropbox\yourproject\node_modules' -Stream com.dropbox.ignored -Value 1
How to optimize largest contentful paint (LCP) on client side
In this article I'm going to show you some strategies I've used to optimize websites on the client side for largest contentful paint (lcp) and first contentful paint (fcp).
What is largest contentful paint and why does it matter?
Largest contentful paint (LCP) is a measure of how long from initial request it takes for your site to render the largest thing on screen. Usually it measures the time it takes your largest image to appear on screen.
First contentful paint (FCP) is a measure of how long from initial request it takes for your site to render anything on screen. In most cases, optimizing LCP will also optimize FCP, so this is the last time I'll mention FCP in this article.
Google Core Web Vitals considers 2.5 seconds on mobile to be a good LCP when loading the site at 4g speeds. This is an extremely high bar to reach, and most sites won't come close without addressing it directly.
It's also important to note that reaching an LCP of 2.5 seconds once is not sufficient to pass. Your pages must achieve an average LCP of under 2.5 seconds over a period of 28 days in order to pass. This means that putting off work on LCP will only be more painful in the future. You need to act now to move your LCP in the right direction.
Why not use server side rendering?
When searching for ways to optimize LCP, I came across various sites that suggested server side rendering. They suggest that rendering the page server side and delivering it fully rendered client side would be the fastest. I know from experience that this is wrong for several reasons.
First, you still need to render on client side even if you deliver a flat html/js/css page. The client side still needs to extract and compile the page, and that takes the bulk of rendering time for modern, js-heavy webpages.
Second, rendering server side can only possibly be faster if your site isn't scaling. Yes, when you have a small number of simultaneous users, it's much faster to render on your server than on an old android. Once you hit hundreds or thousands of simultaneous users that math quickly flips, and it's much faster to render on thousands of client machines, no matter how old they are.
Why not use service workers?
Another suggestion I see is to use service workers to preload content. Please keep in mind that Google only measures the first load of a page. It does not measure subsequent loads. So any technique that improves subsequent loads is irrelevant to Google Core Web Vitals. Yes, this is incredibly frustrating, because frameworks like Next.js give you preloading out of the box.
Optimize images using modern web formats and a CDN
The most important thing you can do to achieve a lower LCP is to optimize the delivery of your images. Unless you use very few images, as I do on this blog, then images are the largest part of the payload for your webpages. They are typically around 10 to 100 times larger than all other assets combined. So optimizing your images should be your number one concern.
First, you need to be using modern web image formats. This means using lightweight formats such as webp, rather than heavier formats such as png.
Second, you need to deliver images from a content distribution network (CDN). Delivering images from edge locations near your users is an absolute must.
Third, you need to show images properly scaled for a user's device. This means requesting images at the specific resolution that will be displayed for a user, rather than loading a larger image and scaling it down with css.
Finally, Google seems to prefer progressive images, which give the user an image experience such as a blurred image before the full image has loaded into memory. There are many robust packages on the web for delivering progressive images.
I suggest you consider ImgIX for optimizing your images. ImgIX is both an image processor and a CDN. With open source components that work with various CMSs, and in various environments, ImgIX is a one stop shop that will quickly solve your image delivery issues. I've used it at two scaling websites, and in both cases it has been extremely impactful.
Deliver the smallest amount of data to each page
After you optimize your images, the next thing to consider is how much data you are sending to the client. You need to send the smallest amount of data that is necessary to render the page. This is typically an issue on list pages.
If you're using out-of-the-box CRUD APIs built in Ruby on Rails, or many other frameworks, then you typically have one template for rendering one type of thing. You might have a product template that renders all the information needed about a product. Then that same product template is used on product detail pages, and on list pages. The problem with that is that much less information is needed on the list pages. So it's imperative that you split your templates into light and heavy templates, then differentiate which are used in which places.
This is more of a backend change than a frontend change, but optimizing frontend performance requires the cooperation of an entire team.
Deliver static assets using a CDN
After putting our images through ImgIX, we stopped worrying about CDNs. We thought that because images were so much larger than the static assets, it wouldn't make much difference to serve static assets from our servers rather than a CDN.
This is true, if you are just beginning to optimize your frontend performance. Putting static assets on a CDN won't lead to a tremendous drop in LCP.
However, once you are trying to get your page load time down to the absolute minimum, every little bit counts. We saved an average of around two tenths of a second on our pages when we put our static assets on a CDN, and two tenths of a second is not nothing.
Another great thing about putting your static assets on a CDN is that it typically requires no code changes. It's simply a matter of integrating the CDN into your continuous integration.
Eliminate third party javascript
Unfortunately, third party javascript libraries are frequently sources of a significant amount of load time. Some third party javascript is not minimized, some pulls more javascript from slow third party servers, and some uses old fashioned techniques such as document.write.
To continue optimizing our load time we had to audit the third party javascript loaded on each page. We made a list of what was loaded where, then went around to each department and asked how they were using each package.
We initially found 19 different trackers on our site. When we spoke with each department we found that 6 of them weren't even being used any more, and 2 more were only lightly used.
So we trimmed down to 11 third party javascript libraries then set that as a hard limit. From then on, whenever anyone asked to add a third party library, they had to suggest one they were willing to remove. This was a necessary step to meet the aggressive performance demands required by Google.
Optimize your bundle size
The final thing to do to optimize your client side load time is to optimize your bundle size. When we talk about bundle size, we're talking about the amount of static assets delivered on your pages. This includes javascript, html, css, and more. Typically, extracting and compiling javascript is what takes the bulk of the time, so that's what you should focus on.
Use code splitting
Code splitting means that your app generates multiple bundles that are potentially different for each page. This is necessary in order to deliver the smallest amount required for a given page. Most modern website transpilers like WebPack will do this automatically.
Forget import *
Stop using "import *" entirely. You should only ever import the methods you are using. When you "import *" you import every bit of code in that module as well as every bit of code that it relies on. In most circumstances, you only need a fraction of that.
It's true, that a feature called tree shaking is able to eliminate some of the cruft in scenarios where you're importing more than you need, but it's sometimes tough to figure out where the tree shaking is working and where it's failing. To do so, you need to run bundle analysis, and comb through it carefully.
It's much easier to simply stop using "import *".
Use composition wisely
I almost named this section "Forget the factory pattern", because the factory pattern creates situations very similar to "import *". In the factory pattern, a method is called that returns an object with all the methods needed to fulfill a responsibility or interface. What I see most often, is a misapplication of the factory pattern whereby programmers are dumping a whole bunch of methods into a pseudo-module then using only one or two methods.
// don't do this
const createDateHelpers = () => {
const formatDate = () => {...};
const dateToUtc = () => {...};
return {
formatDate,
dateToUtc,
}
}
You can see that if you want to call "formatDate", then you need to run "createDateHelpers().formatDate()". This is essentially the same as importing every method in the date helpers module, and again, you are importing all their dependencies as well.
This is where composition can be applied to make an object that gives you the full object when needed, but also allows you to export methods individually.
// use composition
export const formatDate = () => {...};
export const dateToUtc = () => {...};
export default const createDateHelpers = () => {
return {
formatDate,
dateToUtc,
}
};
Render a simplified static page
It's important to note that optimizing your existing pages isn't the only strategy available. Amazon's website crushes Google's Core Web Vitals, even though it isn't very fast. It does this by rendering a simplified static template for the first load, then filling it in with the correct content. So if you visit Amazon, you may see some evergreen content flash up on the page before content specific to you loads in.
That's a fine way to pass Google's Core Web Vitals, but it isn't optimizing the performance of your page. That's tailoring your page to meet a specific test. It's not cheating, but it's not necessarily honoring the intention of Google's UX metrics.
Conclusion
There are two basic categories of changes that are necessary to optimize the client side of a website for largest contentful paint: optimizing delivery of images, and optimizing delivery of code. In this article I've listed several strategies I've used in the past to address both of these categories. These strategies include using progressive images, using CDNs, and delivering as little data and code as is necessary to render a page.
Evan's React Interview Cheat Sheet
In this article I'm going to list some of the things that are useful in React coding interviews, but are easily forgotten or overlooked. Most of the techniques here are useful for solving problems that often arise in coding interviews.
Table of contents
- Imitating componentDidMount using hooks
- Using the previous state in the useState hook
- Using useRef to refer to an element
- Custom usePrevious hook to simplify a common useRef use case
- Vanilla js debounce method
- Use useContext to avoid prop-drilling
Imitating componentDidMount using React hooks
The useEffect hook is one of the more popular additions to the React hooks API. The problem with it is that it replaces a handful of critical lifecycle methods that were much easier to understand.
It's much easier to understand the meaning of "componentDidMount" than "useEffect({}, [])", especially when useEffect replaces componentDidMount, componentDidUpdate, and componentWillUnmount.
The most common use of the useEffect hook in interviews is to replace componentDidMount. The componentDidMount method is often used for loading whatever data is needed by the component. In fact, the reason it's called useEffect is because you are using a side effect.
The default behavior of useEffect is to run after ever render, but it can also be run conditionally using a second argument that triggers the effect when it changes.
In this example, we use the useEffect hook to load data from the omdb api.
// fetch movies. Notice the use of async
const fetchMovies = (newSearchTerm = searchTerm) => {
// See http://www.omdbapi.com/
fetch(`http://www.omdbapi.com/?apikey=${apikey}&s=${newSearchTerm}&page=${pageNumber}`).then(async (response) => {
const responseJson = await response.json();
// if this is a new search term, then replace the movies
if(newSearchTerm != previousSearchTerm) {
setMovies(responseJson.Search);
} else {
// if the search term is the same, then append the new page to the end of movies
setMovies([...movies, ...responseJson.Search]);
}
}).catch((error) => {
console.error(error);
});
};
// imitate componentDidMount
useEffect(fetchMovies, []);
Note that this syntax for using asynchronous code in useEffect is the suggested way to do so.
Using the previous state in the useState hook
The useState hook is probably the easiest and most natural hook, but it does obscure one common use case. For instance, do you know how to use the previous state in the useState hook?
It turns out that you can pass a function into the useState set method and that function can take the previous state as an argument.
Here's a simple counter example.
const [count, setCount] = useState({});
setCount(prevState => {
return prevState + 1;
});
Using useRef to refer to an element
The useRef hook is used to store any mutable value across calls to the component render method. The most common use is to use it to access an element in the DOM.
Initialize the reference using the useRef hook.
// use useRef hook to keep track of a specific element
const movieContainerRef = useRef();
Then attach it to an element in the render return.
<div className={MovieListStyles.movieContainer} ref={movieContainerRef}>
{movies && movies.length > 0 &&
movies.map(MovieItem)
}
</div>
Then you can use the .current property to access the current DOM element for that div, and attach listeners or do anything else you need to do with a div.
// set up a scroll handler
useEffect(() => {
const handleScroll = debounce(() => {
const scrollTop = movieContainerRef.current.scrollTop;
const scrollHeight = movieContainerRef.current.scrollHeight;
// do something with the scrolling properties here...
}, 150);
// add the handler to the movie container
movieContainerRef.current.addEventListener("scroll", handleScroll, { passive: true });
// remove the handler from the movie container
return () => movieContainerRef.current.removeEventListener("scroll", handleScroll);
}, []);
Custom usePrevious hook to simplify a common useRef use case
The useRef hook can be used to store any mutable value. So it's a great choice when you want to look at the previous value in a state variable. Unfortunately, the logic to do so is somewhat tortuous and can get repetitive. I prefer to use a custom usePrevious hook from usehooks.com.
import {useEffect, useRef} from 'react';
// See https://usehooks.com/usePrevious/
function usePrevious(value) {
// The ref object is a generic container whose current property is mutable ...
// ... and can hold any value, similar to an instance property on a class
const ref = useRef();
// Store current value in ref
useEffect(() => {
ref.current = value;
}, [value]); // Only re-run if value changes
// Return previous value (happens before update in useEffect above)
return ref.current;
}
export default usePrevious;
Using it is as simple as one extra line when setting up a functional component.
// use the useState hook to store the search term
const [searchTerm, setSearchTerm] = useState('orange');
// use custom usePrevious hook
const previousSearchTerm = usePrevious(searchTerm);
Vanilla js debounce method
Okay, this next one has nothing to do with React, except for the fact that it's a commonly needed helper method. Yes, I'm talking about "debounce". If you want to reduce the jittery quality of a user interface, but you still want to respond to actions by the user, then it's important to throttle the rate of events your code responds to. Debounce is the name of a method for doing this from the lodash library.
The debounce method waits a preset interval until after the last call to debounce to call a callback method. Effectively, it waits until it stops receiving events to call the callback. This is commonly needed when responding to scroll or mouse events.
The problem is that you don't want to install lodash in a coding interview just to use one method. So here's a vanilla javascript debounce method from Josh Comeau.
//
const debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
callback.apply(null, args);
}, wait);
};
}
export default debounce;
Here's an example of how to use it to update a movie list when a new search term is entered.
// handle text input
const handleSearchChange = debounce((event) => {
setSearchTerm(event.target.value);
fetchMovies(event.target.value);
}, 150);
return (
<div className={MovieListStyles.movieList}>
<h2>Movie List</h2>
<div className={MovieListStyles.searchTermContainer}>
<label>
Search:
<input type="text" defaultValue={searchTerm} onChange={handleSearchChange} />
</label>
</div>
<div
className={MovieListStyles.movieContainer}
ref={movieContainerRef}
>
{movies && movies.length > 0 &&
movies.map(MovieItem)
}
</div>
</div>
);
Use useContext to avoid prop-drilling
The last thing an interviewer wants to see during a React coding interview is prop drilling. Prop drilling occurs when you need to pass a piece of data from one parent component, through several intervening components, to a child component. Prop drilling results in a bunch of repeated code where we are piping a variable through many unrelated components.
To avoid prop drilling, you should use the useContext hook.
The useContext hook is a React implementation of the provider pattern. The provider pattern is a way of providing system wide access to some resource.
It takes three code changes to implement the useContext hook. You've got to call createContext in the parent component that will maintain the data. Then you've got to wrap your app with a special tag.
export const DataContext = React.createContext()
function App() {
const data = { ... }
return (
<div>
<DataContext.Provider value={data}>
<SideBar />
<Content />
</DataContext.Provider>
</div>
)
}
Then you've got to import the context in the child component, and call useContext to get the current value.
import DataContext from '../app.js';
...
const { data } = React.useContext(DataContext);
What has made you stumble in React interviews?
What are some other common ways to make mistakes in React coding interviews? Send me your biggest React coding headaches on Twitter @EvanXMerz.
Introduction to JavaScript
In this post, I'm going to introduce the JavaScript programming language and show you a few ways it's commonly used on the web.
WARNING: Some prior programming required!
I'm not introducing programming in general. This isn't a good first tutorial for someone who has never written a line of code in their life. I'm assuming that you know what functions are, and that methods and subroutines are the same thing. I'm assuming that you've written at least a little of some kind of code in the past.
TLDR: JavaScript is the engine of a webpage
HTML is used to define the structure of a web page. CSS defines the look and feel of a page. JavaScript defines how a page acts and responds to user input.
If a web page is a car, then HTML is the frame, CSS is the body, and JavaScript is the engine.
A haiku about JavaScript
Dearest Javascript,
You are my favorite tool
that costs me nothing.
Why not Java?
JavaScript has nothing to do with Java. The name JavaScript came about as an attempt to steal some of the hype around the Java programming language in the 1990s. In the 30 years since then, their positions have entirely flipped, with JavaScript now being the programming language that sees tons of innovation and market support, while Java is withering. Why has this happened? Why has JavaScript been so successful?
In my opinion, Java has been uniquely unsuccessful as a language because it has refused to grow and innovate. Java set some standards and some conventions for the language, and even thirty years later, the committees who guide specification for that language continue to be far too conservative.
If you're a Java programmer and you don't agree with me, then I encourage you to try Microsoft C#. That's the fun language that Java could have been.
And why is that?
C# and JavaScript have become incredibly open-ended. You can write code in lots of different ways, using features borrowed from other cool languages and frameworks. Both are sort of like Frankenstein's monster inasmuch as they are collections of mismatched parts that come together to form a surprisingly intimidating beast.
And this scares some people.
But, much like Shelley's actual monster, they are actually quite nice, and I hope to convey that to you in this introduction.
But what is JavaScript?
It's a programming language that runs in the web browser and on nearly every device that exists. On a webpage, you include JavaScript within the script element, or by including code in an external file. There's more to it than that, but let's learn by doing.
A JavaScript function
One of the key responsibilities of JavaScript is to respond to user input. Beside a few basic interactions, like clicking a link or a dropdown, every action that occurs on a webpage is handled by custom event handlers that are usually written in JavaScript. In this case, we're going to write a script that handles a click event.
When the user clicks on something, the browser creates what is called a click event. It then runs a bit of code called a function that is tied to that event. Here's the click handler function that we're going to write.
function showWarningOnClick() {
alert("Warning! The elders of the internet have been notified!");
}
That might look like nonsense to you, so let's break it down. This graphic shows how each of the pieces come together.
The first line is the function declaration. The word "function" declares that the next word is the name of a function. So "showWarningOnClick" is the name of the function. The parentheses, "()", would enclose the method arguments, if we needed any. The open bracket, "{", indicates that the next line will be the first line of the function body.
The second line is the actual code that is executed by this function. In this case, it calls another function. It calls "alert", with the argument "Warning! The elders of the internet have been notified!". Notice that the argument is enclosed with parentheses. Then the line ends with a semicolon. Most lines of JavaScript code must end in a semicolon.
That's the code that we will run when a button is clicked. We will see in the next section how we can trigger that function using a button.
A JavaScript event
The following line of code creates a button and adds a handler for the onClick event.
<button onClick="showWarningOnClick();">Don't click me</button>
Notice that the button isn't created using a div or span element, as you might expect. When creating buttons, it's good to use the button element if you can. This element is friendly to screen readers, and other accessibility technology.
The only attribute given is "onClick", and it's set to call the function that we defined earlier. The onClick attribute tells the browser what to do when the button is clicked.
It's important to note that onClick events can be attached to any elements. They aren't something unique to buttons. You can attach methods to the onClick attributes of divs or spans, or any other HTML elements.
Responding to a click event in JavaScript
So let's put it all together. The code should look something like this. And as a reminder, please type this in, don't copy paste it. The point is to learn, and you will learn and remember more if you type it out.
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Hello World</title>
<script>
function showWarningOnClick() {
alert("Warning! The elders of the internet have been notified!");
}
</script>
</head>
<body>
<div>
<button onClick="showWarningOnClick();">Don't click me</button>
</div>
</body>
</html>
When you run it, by double clicking it and opening it in a web browser, you should see a single button in the upper left that says "DOn't click me". Click it, and you should see a little popup with the message "Warning! The elders of the internet have been notified!".
Summary
JavaScipt is a fun, open-ended programming language that is particularly useful in web browsers. In this post we learned about JavaScript functions and event handlers.
More
Here are some other introductions to JavaScript from around the web. I suggest that you spend time to check them out, even if you found this really easy. It's good to go over the fundamentals often, and it's particularly good to get multiple perspectives.
- MDN introduction to JavaScript. I link to MDN in every post because it's the most reliable source.
- Introduction to JavaScript from javascript.info.
- Introduction to JavaScript from freecodecamp. Some of what he says in this video is a bit misleading, but it's still a good place to start.
What is the DOM?
In this post, I'm going to introduce the Document Object Model, which is better known as the DOM.
TLDR: The DOM is the way a webpage is represented within a browser
The DOM is a bit of structured data that holds a representation of a webpage in order to enable programs to be able to manipulate it.
It's a bit like a 3d model that you might use in a 3d printer. The 3d printer is the display device, like a web browser. The 3d model is needed by the 3d printer for it to be able to do anything. You can manipulate the 3d model to make the 3d printer do what you want.
Why should we care about the DOM?
Why does the DOM matter? Can't we go about writing web code without knowing about the DOM?
The DOM matters because it is the representation of a web page that we can work with in JavaScript. It's true that, as a web programmer of over twenty years now, I have probably explicitly thought about the DOM only a handful of times. But I use it every day, and in the dark ages of JavaScript, we had to work with it very directly. These days we use packages like React or jQuery to wrap add a convenient layer of abstraction above the DOM. Still, in this post I'm going to show you how to use the DOM the old fashioned way.
This is still very important to know because occasionally you do have to do it, even when using the modern libraries for interacting with the DOM.
Finding elements in the DOM in JavaScript
In JavaScript, the DOM is stored as a global variable named "document". So when you want to do something with the DOM, you use the document variable.
One of the things you must be able to do is find the HTML element that you want to work with. The document gives us several handy methods to find elements by their id or class attributes. The getElementById method returns the first element found with the matching id attribute. Since id attributes should be unique, this should be the only element with that id in the document.
So to find a document with the id "elephant", you'd use the following line of code.
let elephantElement = document.getElementById("elephant");
This line of code contains a few things that might look unfamiliar to anyone who hasn't used JavaScript before. Here's a diagram that breaks it down.
The word "let" is a reserved word that indicates that the next word is a locally scoped variable. Don't worry if you don't know what "locally scoped" means. In this case, it just means that it's not a global variable, like document. Then the word "elephantElement" is the variable name, and the equals sign indicates that whatever is returned by the expression on the right should be stored in the variable on the left.
The id attribute isn't the only way to find elements. JavaScript also specifies a way to get elements with the same class attribute using the getElementsByClassName method. Notice that "Elements" is plural because the method returns an array, as many elements may share a class.
let elephantElements = document.getElementsByClassName("elephant");
The structure of this line of code is essentially the same as the previous one. The only difference is the name of the method that is called, the name of the variable, and the type of the variable.
How to modify a webpage using JavaScript
You can also use JavaScript to change the content of a webpage. When you've located the element you want to change, you can use the innerHTML method to get or set the content of that element. Like much of the JavaScript in this post, this is considered a bad thing to do these days. Typically you'd use the methods in a UI framework like React to mutate the page.
let elephantElement = document.getElementById("elephant");
elephantElement.innerHTML = "Is this an elephant?"
You can also add elements to the page using the createElement and append methods. This blurb appends a copyright notice to a blog post.
let blogPost = document.getElementById("post");
let copyrightNotice = document.createElement("p")
copyrightNotice.innerHTML = "Copyright " + new Date().getFullYear() + " by the author";
blogPost.append(copyrightNotice);
Let's put that into a real HTML file to see it in action. Here's the full example. If you've typed it in correctly, then you should see a copyright notice at the bottom of a paragraph of latin.
<!DOCTYPE html>
<html>
<head>
<title>How to modify the DOM using JavaScript</title>
</head>
<body>
<div id="post">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
<script>
let blogPost = document.getElementById("post");
let copyrightNotice = document.createElement("p")
copyrightNotice.innerHTML = "Copyright " + new Date().getFullYear() + " by the author";
blogPost.append(copyrightNotice);
</script>
</body>
</html>
Summary
The DOM is the internal model of a webpage stored in the web browser. It can be manipulated using JavaScript methods such as getElementById, innerHTML, and append.
More
How to Debug a webpage in Chrome
In this post, I'm going to show you several ways to debug javascript in Google Chrome.
TLDR: Shift + ctrl + c
Open the javascript console using the shortcut shift + ctrl + c. That means, press all three at the same time. If you're on a Mac, use cmd rather than ctrl.
Inspecting an element to add new styles
Open the dom example that we wrote in What is the DOM?. In this post, we'll use that page to explore three ways to find out what's going on in a webpage. The code should look something like this.
<!DOCTYPE html>
<html>
<head>
<title>How to modify the DOM using JavaScript</title>
</head>
<body>
<div id="post">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
<script>
let blogPost = document.getElementById("post");
let copyrightNotice = document.createElement("p")
copyrightNotice.innerHTML = "Copyright " + new Date().getFullYear() + " by the author";
blogPost.append(copyrightNotice);
</script>
</body>
</html>
After opening it in Chrome, right click on the copyright notice, then click "Inspect" in the popup menu. The html for the element you clicked should appear in the "Elements" tab of the browser console. It should look something like this but there are several places where the console can appear.
Any time you are confused about an element, inspecting it is a good place to start. In the browser console, you should be able to see the html for the element, as well as any attached css. In fact, you can actually add new styles using the browser console. Notice in the "Styles" tab where it says "element.style" and there are empty braces. Any CSS you type in there will be applied to the selected element. Styles can also be toggled on and off. This can be exceptionally handy when trying to figure out why your page doesn't look quite right.
Go ahead and try it. Enter "font-weight: 800" into element.style. What happens?
Debugging using console.log
The most basic way to debug JavaScript is to put some console.log statements into your code, then open the console and see what they print. Calling console.log tells the browser to print whatever you pass in in the JavaScript console. You can console.log just about anything, but the web browser will format variables automatically if you put them in an object using curly braces.
So the following console.log call isn't very useful.
console.log('HERE!');
Whereas the following console.log call will allow you to examine the object passed int.
console.log({ confusingVariable });
Add the following line of code to the dom example after it sets the copyrightNotice variable.
console.log({ copyrightNotice });
Then reload the page and use shift + ctrl + c to open the browser console, or shift + cmd + c on a Mac. You may have to expand the window and click over to the tab that says "Console".
You should see something that says "{ copyrightNotice: p }", and if you expand it, then you should be able to see all the properties associated with that variable. It's not particularly useful in this simple example, but I use this technique virtually every day as a web programmer.
Debugging using the Chrome debugger
Keep the dom example open in Chrome. Then press shift + ctrl + c to open the browser console. Navigate over to the "Sources" tab. This tab is the built in debugger provided by Google Chrome. In this debugger, you can add breakpoints to your code, and the browser will stop at the breakpoints and allow you to examine the state of your code. It's okay if this looks very confusing. We don't need to do much with it.
There are three things you need to be able to do. First, you need to be able to navigate to the sources tab. Then you need to find the line numbers, because you need to click them. This is much more difficult in a typical web page that has many files. Finally, you need to know how to continue running the page after it stops at your breakpoints.
After you've opened up the page, click line 12 to set a breakpoint there. In this case, that code has already been run, so reload the page to make it run again. When you do so, you should see something like the following.
Notice the area that I've circled in that screenshot. It's where you can see the state of your code. All of your variables should appear there and you should also see their values. Notice that copyrightNotice is undefined in this case. That's because Chrome pauses before running the code at the line where you placed your breakpoint. If you place a breakpoint on the next line, then press the play/pause button to resume execution, the value for copyrightNotice should be filled in.
The Chrome debugger is a great way to examine the state of your code.