Learning path · Level 5 · Tutorial 1

Read and Update the DOM Safely

Read and modify a local page through JavaScript, including its text, controls, classes, and child elements, while keeping user-supplied text separate from HTML markup.

  • Level: Beginner
  • Duration: 20 min
  • 9 steps
Start tutorial Back to the path
Read and Update the DOM Safely

What you will go through

  1. Meet the Page’s DOM Tree Build the local Reading List page and connect its visible parts to the live DOM tree. 2 min
  2. Select One Element and Handle No Match Select one intended DOM element with `querySelector()` and safely handle a selector that returns `null`. 2 min
  3. Select and Inspect Several Elements Select all matching book elements, inspect their NodeList, and distinguish an empty NodeList from a null single-element selection. 2 min
  4. Read and Replace Text Without Parsing HTML Read and replace a message element’s text while keeping HTML-like input as literal visible characters. 3 min
  5. Read, Set, and Remove Attributes Inspect, update, and remove an ordinary text attribute on the Reading List element, including the difference between an empty value and a missing attribute. 2 min
  6. Use Properties for Current Control Values Read and update the current values and availability of Reading List controls through DOM properties, while distinguishing them from HTML attributes. 2 min
  7. Update Classes Predictably Add, remove, and force CSS classes on an existing Reading List item while preserving its base styling. 2 min
  8. Create and Change Child Elements Create, insert, replace, and remove Reading List child elements while keeping structural changes scoped to the list. 3 min
  9. Apply and Verify a Safe DOM Update Complete a local previewBook() helper, run it with HTML-like input, and verify that the page updates safely without parsing supplied text as markup. 3 min

What you will learn

  • Select one or several elements and handle a selector that finds no match.
  • Read control values and update text, attributes, properties, and classes.
  • Create, insert, replace, and remove elements using DOM methods.
  • Verify that a string containing HTML-like characters appears as literal text rather than becoming markup.

Before you start

  • Create and Inspect a Local HTML Page
  • Apply CSS with Stylesheets and Selectors
  • Write Functions with Parameters and Lexical Scope
  • Represent Records and Lists with Objects and Arrays
  • Transform Arrays with Callback Functions

Step 1 of 9

Meet the Page’s DOM Tree

Build the local Reading List page and connect its visible parts to the live DOM tree.

See the page as a DOM tree

A live tree for the current page

A browser represents the page currently on screen as a live DOM tree. The top entry point is the document object. Inside it are element nodes, such as main, h1, p, ul, and li, plus text nodes that hold words such as “Reading List.”

Tree relationships describe structure: the ul is the parent of its li children, and the two book li elements are siblings. This live tree is what browser JavaScript will work with throughout this tutorial.

Visible page and DOM tree

A diagram connects a Reading List page to a DOM tree. The list element has two sibling list-item children.

The visible page and the DOM tree describe the same current document in different forms.

Set up the local page

Create the Reading List page

In one local folder, create these three files. Paste the complete source below into index.html. It links the stylesheet and loads script.js with defer. Keep this starter page unchanged as later steps replace only the contents of script.js.

index.html

index.html

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reading List</title>
    <link rel="stylesheet" href="styles.css">
    <script src="script.js" defer></script>
  </head>
  <body>
    <main>
      <h1 id="page-title">Reading List</h1>
      <p id="message">Two books ready.</p>

      <ul id="reading-list">
        <li class="book">HTML Basics</li>
        <li class="book">CSS Layouts</li>
      </ul>

      <label for="book-title">Book title</label>
      <input id="book-title" type="text" value="JavaScript Basics">

      <label>
        <input id="highlight-book" type="checkbox" checked>
        Highlight new book
      </label>

      <button id="preview-button" type="button" disabled>Preview</button>
    </main>
  </body>
</html>

Add styling and observe the live document

styles.css

styles.css

css
body {
  font-family: system-ui, sans-serif;
  margin: 0;
  padding: 2rem;
  color: #1f2933;
}

main {
  max-width: 38rem;
  margin: 0 auto;
}

label,
input,
button {
  display: block;
  margin-top: 0.75rem;
}

.book {
  margin: 0.5rem 0;
  padding: 0.5rem;
}

.is-highlighted {
  background: #fff3bf;
  border: 2px solid #8a5a00;
}

button:disabled {
  opacity: 0.6;
}

script.js

script.js

javascript
console.log('DOM ready:', document.title);
Expected output
DOM ready: Reading List

Save, load, and reload

Open index.html in your browser and open the Console. The message confirms that script.js can reach the current document.

The saved index.html is source on your computer; the DOM is the browser's current in-memory tree. A temporary DevTools edit changes the current page, not the saved file. Reloading reconstructs the DOM from the saved HTML and runs script.js again.

Tip

Current page versus saved file

Use reload to return to the saved starting page after experimenting in DevTools. Reload is not a way to save a DOM change back into index.html.

Check your DOM map

Map the page to the DOM tree

Match each page part or DOM fact to its description.

Tap an item and then its matching pair.

Step 2 of 9

Select One Element and Handle No Match

Select one intended DOM element with querySelector() and safely handle a selector that returns null.

Ask the document for one element

One selector, one result

document.querySelector() starts at the document and accepts a CSS selector. It returns the first matching element.

Store that result in a clearly named variable before you use it. On the Reading List page, '#page-title' identifies the page heading.

A selector can be valid but still find nothing. In that case, querySelector() returns null, not an element.

How querySelector chooses

A diagram showing an ID selector returning one heading, a selector with multiple possible matches returning the first list item, and a missing selector returning null.

querySelector() returns the first match it encounters, or null when there is no match.

Keep the missing case separate

script.js

Replace the current contents of script.js with this code, save, then refresh the local page. Open the browser Console to inspect the result.

javascript
const heading = document.querySelector('#page-title');

if (heading === null) {
  console.log('Heading was not found.');
} else {
  console.log(heading);
}
Expected output
<h1 id="page-title">Reading List</h1>

The Console’s element display varies by browser. The important result is that it represents the element with id `page-title`.

Guard the result before using it

Check for null before accessing an element’s properties or methods. The else branch is the only place where this script uses heading, so it runs only when an element was found.

This separates a normal missing-element case from code that expects an element. It also makes the variable’s purpose clear: heading holds the selected heading or null.

Tip

Why the guard matters

A selector that finds no element is not an error by itself. The error happens when code treats null as though it were an element—for example, by trying to read one of its properties.

Check the first-match rule

Predict a first match

On the starter page, what does this expression return?

document.querySelector('.book')

Practice: handle no match

Complete a guarded missing selection

Complete the guard for this deliberately missing selector. Make the script report the branch that runs when no element is selected.

javascript
const heading = document.querySelector('#missing-heading');

if () {
  console.log();
} else {
  console.log('An element was selected.');
}

Step 3 of 9

Select and Inspect Several Elements

Select all matching book elements, inspect their NodeList, and distinguish an empty NodeList from a null single-element selection.

Collect the book elements

A NodeList contains all matches

querySelectorAll() finds every element that matches a CSS selector and returns a NodeList in document order. On the Reading List page, document.querySelectorAll('#reading-list .book') finds the two book li elements: first HTML Basics, then CSS Layouts.

A NodeList is not an Array, but this NodeList provides useful features including .length and .forEach(). Each entry refers to an actual element in the current DOM tree.

From matching elements to a NodeList

A diagram shows two list-item elements in a DOM tree flowing into a two-entry collection in the same order.

querySelectorAll() collects every matching element in document order.

Count, then iterate

script.js — count and visit every book

Replace the previous example in script.js, refresh the page, and inspect the output in the Console.

javascript
const books = document.querySelectorAll('#reading-list .book');

console.log(books.length);

books.forEach((book) => {
  console.log(book);
});
Expected output
2
<li class="book">HTML Basics</li>
<li class="book">CSS Layouts</li>

Browsers may display each logged element as an expandable DOM element rather than exactly as shown.

Tip

A static collection

A returned NodeList is static: its membership does not automatically gain newly matching elements later. Its existing entries still refer to their DOM elements.

When you later need a selection that includes changed list membership, run querySelectorAll() again. For now, use .length to count this selection and forEach() to visit each book.

Handle an empty selection

script.js — compare no-match results

A selector can be valid but match nothing. Replace script.js with this example, refresh, and inspect the Console.

javascript
const missingOne = document.querySelector('#missing-book');
const missingMany = document.querySelectorAll('#missing-book');

console.log(missingOne);
console.log(missingMany.length);

missingMany.forEach((book) => {
  console.log(book);
});
Expected output
null
0

The first line is the single-element result. The second line is the number of entries in the multi-element result.

No match: single versus several

querySelector() returns null when it finds no element. In contrast, querySelectorAll() returns an empty NodeList, not null.

An empty NodeList has length 0, and its forEach() runs no callbacks because there are no entries to visit.

Practice: inspect the selections

Check all matches and no matches

Complete this script.js check so it logs each matched book element and confirms the two no-match results.

javascript
const books = document.querySelectorAll('#reading-list .book');

books.forEach((book) => {
  console.log();
});

const oneMissing = document.querySelector('#missing-book');
const manyMissing = document.querySelectorAll('#missing-book');

console.log(oneMissing === );
console.log(manyMissing.length === );

Step 4 of 9

Read and Replace Text Without Parsing HTML

Read and replace a message element’s text while keeping HTML-like input as literal visible characters.

Text content is data, not markup

Read and replace element text

textContent reads the text contained by an element. On the starter page, #message contains the text Two books ready., so its textContent is that string.

Assigning to textContent replaces the selected element’s child content with a text node. It is useful when you want displayed characters to remain text—not page structure.

Assign text safely

script.js

In script.js, replace the previous example with this code. Save the file and refresh the page. The Console first shows the message’s original text; then the page updates.

javascript
const message = document.querySelector('#message');

if (message !== null) {
  console.log(message.textContent);
  message.textContent = '<strong>New book</strong> & notes';
}
Expected output
Two books ready.

The Console output is the original message. On the page, the angle brackets, tag name, and ampersand are all visible characters.

Literal text versus parsed structure

A comparison between a paragraph holding the safety-test string as one text node and a contrasting tree that contains a nested strong element.

With textContent, the safety-test string is displayed literally. The resulting DOM tree has text inside #message, not a new strong element.

Warning

Replace only the intended container

textContent replaces every child node inside the element you assign to. Keep the update narrowly scoped: update #message, not a parent that also contains the input, checkbox, button, or reading list.

Do not substitute an HTML-parsing API when displaying supplied text. This step deliberately uses textContent so HTML-like characters remain ordinary visible characters.

Complete a safe text update

Practice: choose the text property

Complete the property name so the supplied string is inserted as literal text into the already selected message element.

javascript
if (message !== null) {
  message. = '<strong>New book</strong> & notes';
}

Verify the safety test

Check the page and DOM result

After message has been selected and guarded, choose the completion and outcome that correctly handle this safety-test string:

const safetyTest = '<strong>New book</strong> & notes';

message.____

Step 5 of 9

Read, Set, and Remove Attributes

Inspect, update, and remove an ordinary text attribute on the Reading List element, including the difference between an empty value and a missing attribute.

Inspect the title attribute

Read an ordinary attribute

An attribute is named information attached to an element in the DOM tree. For the Reading List, work with the ordinary text attribute title on #reading-list.

Use getAttribute('title') to read it. The result is a string when the attribute exists, or null when it is absent. That makes “no title attribute” different from a title whose value happens to be an empty string.

An attribute belongs to an element

A DOM tree diagram highlights an unordered list and contrasts a present attribute with an absent attribute.

The same element can have a title attribute with a string value, or no title attribute at all.

Attribute method guide

Use these methods on the selected element.

Methods for the Reading List element
GoalMethodResult
Read the titlegetAttribute('title')String or null
Add or change the titlesetAttribute('title', value)Attribute has the value
Make the title absentremoveAttribute('title')Attribute is removed

script.js: inspect, set, and remove title

Replace script.js with this code, save, refresh the local page, and inspect the Console. The final call leaves the Reading List with its fixed title attribute.

javascript
const readingList = document.querySelector('#reading-list');

if (readingList !== null) {
  console.log(readingList.getAttribute('title'));

  readingList.setAttribute('title', 'Books to explore');
  console.log(readingList.getAttribute('title'));

  readingList.removeAttribute('title');
  console.log(readingList.getAttribute('title'));

  readingList.setAttribute('title', 'Books to explore');
}
Expected output
null
Books to explore
null

The first and third lines are `null` because the title attribute is absent at those moments. The final setAttribute call restores the title without logging another line.

Change it deliberately

Empty is not absent

setAttribute() adds the attribute when it is missing and updates it when it already exists. In contrast, removeAttribute() removes the attribute itself; assigning '' only creates or keeps an attribute whose value is an empty string.

After refreshing, use the browser’s Elements inspector to select #reading-list. Its title attribute should be Books to explore after the previous script finishes.

script.js: compare empty and absent

Now replace script.js with this version. It compares an empty attribute value with an absent attribute, then restores the fixed title.

javascript
const readingList = document.querySelector('#reading-list');

if (readingList !== null) {
  readingList.setAttribute('title', '');
  console.log(readingList.getAttribute('title') === '');

  readingList.removeAttribute('title');
  console.log(readingList.getAttribute('title'));

  readingList.setAttribute('title', 'Books to explore');
}
Expected output
true
null

`true` confirms that the first read is an empty string. The second read is `null` because removeAttribute removes the title attribute entirely.

Warning

A safety boundary

Use these methods for the ordinary title attribute shown here. setAttribute() changes an attribute; it does not decide whether every possible attribute name and value is safe or appropriate. Other kinds of attributes can require additional rules, which are outside this step.

Practice: choose the operation

Check your attribute choices

Match each Reading List title situation with its correct method or result.

Tap an item and then its matching pair.

Step 6 of 9

Use Properties for Current Control Values

Read and update the current values and availability of Reading List controls through DOM properties, while distinguishing them from HTML attributes.

Read the live property

Properties describe the current page

A control has live properties on its DOM element. They describe what the control contains or does now:

  • bookTitle.value is the text currently in the input.
  • highlightBook.checked is the checkbox’s current boolean state.
  • previewButton.disabled is whether the button is currently unavailable.

HTML attributes establish markup and often initial defaults. After someone edits a control, read its property—not its original attribute—to learn the current state.

Markup defaults and live control state

Diagram showing that an input value and checkbox checked state can change on the page, while attributes describe initial markup; a disabled attribute is present or absent.

Properties report the control’s current state. For disabled, the attribute’s presence is what matters.

Property quick guide

Use the property that matches the control state you need.

The text input and checkbox attributes set initial defaults; their properties can later differ after a manual edit.
ControlLive propertyWhat the matching attribute represents
Text input`value` (string)Initial value
Checkbox`checked` (boolean)Initial checked state
Button`disabled` (boolean)Presence means disabled

Change value, checked, and disabled

Practice property reads and updates

Replace script.js with this complete file, save it, and reload the local page. The first three log lines read the starter state. The assignments then change the current page: they do not rewrite index.html.

Read and update live properties

script.js

javascript
const bookTitle = document.querySelector('#book-title');
const highlightBook = document.querySelector('#highlight-book');
const previewButton = document.querySelector('#preview-button');

if (bookTitle !== null && highlightBook !== null && previewButton !== null) {
  console.log(bookTitle.value);
  console.log(highlightBook.checked);
  console.log(previewButton.disabled);

  bookTitle.value = 'DOM Patterns';
  highlightBook.checked = false;
  previewButton.disabled = false;

  console.log(bookTitle.value);
  console.log(highlightBook.checked);
  console.log(previewButton.disabled);
}
Expected output
JavaScript Basics
true
true
DOM Patterns
false
false

The first three lines are the starter values. The remaining lines are the values immediately after the property assignments.

Warning

Boolean attribute caution

disabled is a boolean property. Set previewButton.disabled = false to enable the button.

Do not treat disabled="false" in HTML as enabled. disabled is a boolean attribute: if it is present at all—even with the text "false"—the button starts disabled. Removing the attribute, or setting the DOM property to false, enables it.

Check the page as it is now

Inspect a manual edit without refreshing

Keep the page open after the script runs. Change the input text to Changed in the page and check the checkbox yourself. Do not reload.

Then use the Console to inspect the current page. No event listener is needed: the browser has already applied your manual edits, and the properties expose their current state.

Compare live state with an attribute

Console

javascript
console.log(bookTitle.value);
console.log(bookTitle.getAttribute('value'));
console.log(highlightBook.checked);
console.log(previewButton.disabled);
Expected output
Changed in the page
JavaScript Basics
true
false

This output assumes you typed the specified input text and checked the checkbox after running the previous script.

Practice: live state versus defaults

Apply the correct properties

Assume the input has been manually edited without a reload, and the button’s initial HTML includes disabled="false". Complete the code to record the button’s initial state, read both title forms, and enable the button.

javascript
const buttonStartsDisabled = previewButton.disabled === ;
const currentTitle = bookTitle.;
const originalTitle = bookTitle.('value');
previewButton. = ;

Step 7 of 9

Update Classes Predictably

Add, remove, and force CSS classes on an existing Reading List item while preserving its base styling.

Add a class without replacing another

Keep the base class

An element can carry more than one CSS class. On a Reading List item, book supplies the base styling, while is-highlighted supplies optional emphasis.

classList.add('is-highlighted') adds that one class token without replacing book. Likewise, classList.remove('is-highlighted') removes only the named class. Pass class names to classList without the CSS dot.

Work with an existing list item in this step. The explicit null check ensures class operations run only when the intended element was found.

One element, layered classes

A book list item keeps its base appearance while an optional highlighted background and border are added and removed.

The emphasis class is an addition to the base book class, not a replacement for it.

Add and remove one class

Replace script.js with this code, save, and refresh the local page. The first Reading List item receives emphasis, then returns to its base appearance. The Console records its class attribute after each change.

javascript
const book = document.querySelector('#reading-list .book');

if (book === null) {
  console.log('Book was not found.');
} else {
  console.log(book.getAttribute('class'));

  book.classList.add('is-highlighted');
  console.log(book.getAttribute('class'));

  book.classList.remove('is-highlighted');
  console.log(book.getAttribute('class'));
}
Expected output
book
book is-highlighted
book

With the unchanged starter page, the first book begins with only its base class.

Make a checkbox decision predictable

Reverse versus force

classList.toggle() has two different behaviors:

  • With one argument, it reverses whether that class is present. Run the same call twice and the second call undoes the first.
  • With a second boolean argument, it forces a result: true makes the class present and false makes it absent.

For a one-time update based on the control as it is now, use highlightBook.checked as the second argument. This reads the checkbox’s current property; it does not create an automatic response to later checkbox changes.

classList choice guide

Each operation affects only the named class token, so the base book class remains available for styling.

Use the boolean form when a class must match a known boolean value.
Call formResult for is-highlighted
add('is-highlighted')Present; other classes remain
remove('is-highlighted')Absent; other classes remain
toggle('is-highlighted')Reverses current presence
toggle('is-highlighted', true)Present every time
toggle('is-highlighted', false)Absent every time

Compare repeated toggles

Replace the previous script.js with this version, save, and refresh. Watch the first book and inspect the Console. Two ordinary toggles alternate its appearance. The two boolean-forced calls both use the checkbox’s current checked value, so they leave the same result each time.

javascript
const book = document.querySelector('#reading-list .book');
const highlightBook = document.querySelector('#highlight-book');

if (book === null || highlightBook === null) {
  console.log('Required element was not found.');
} else {
  book.classList.toggle('is-highlighted');
  console.log(book.getAttribute('class'));

  book.classList.toggle('is-highlighted');
  console.log(book.getAttribute('class'));

  book.classList.toggle('is-highlighted', highlightBook.checked);
  console.log(book.getAttribute('class'));

  book.classList.toggle('is-highlighted', highlightBook.checked);
  console.log(book.getAttribute('class'));
}
Expected output
book is-highlighted
book
book is-highlighted
book is-highlighted

The starter checkbox is checked after a refresh, so its current value is true in this run.

Apply a repeatable class update

Practice: preserve and force

Complete the two classList method calls. This update runs twice: the existing item must retain its base styling, and its highlight state must match the checkbox’s current boolean after both runs.

javascript
const book = document.querySelector('#reading-list .book');
const highlightBook = document.querySelector('#highlight-book');

const applyClasses = () => {
  if (book === null || highlightBook === null) {
    return;
  }

  book.classList.('book');
  book.classList.('is-highlighted', highlightBook.checked);
};

applyClasses();
applyClasses();

Step 8 of 9

Create and Change Child Elements

Create, insert, replace, and remove Reading List child elements while keeping structural changes scoped to the list.

Create and append a list item

Build before you insert

document.createElement('li') makes an element object, but does not put it into the DOM tree. The new element has no parent until code inserts it.

Prepare the detached li first: assign its textContent and add its base book class. Then call append() on #reading-list. append() inserts the configured element after the list’s existing children.

From detached element to list child

Diagram of a detached list item being inserted at the end of an unordered list that already has two list-item children.

Creating an element and inserting it are separate actions: configure the detached element, then append it to the list.

script.js — append a configured book

Replace script.js with this code, save, and refresh the unchanged starter page. The original two books stay in place and the configured new book appears last.

javascript
const readingList = document.querySelector('#reading-list');

if (readingList === null) {
  console.log('Reading list was not found.');
} else {
  const newBook = document.createElement('li');

  newBook.textContent = 'JavaScript Basics';
  newBook.classList.add('book');

  readingList.append(newBook);
}
Expected output
Visible #reading-list:
HTML Basics
CSS Layouts
JavaScript Basics

Expected visible page result: the list has three items in this order. The message and controls are unchanged.

Replace, remove, and select again

Change only the list’s children

Child-element methods affect the children of the element that receives the method. Target readingList, not an ancestor that also contains the input, checkbox, and button.

replaceChildren(newBook) removes the list’s current children and inserts the supplied child. With no arguments, replaceChildren() clears the list. book.remove() detaches that selected list item from the document.

A NodeList returned earlier is static. After an update, run querySelectorAll() again when you need a fresh selection that includes the current list membership.

Child-operation guide

These operations change list content without replacing the surrounding controls.

Each effect is limited to the selected list or selected list item.
MethodEffect
append(newBook)Adds a child after existing children
replaceChildren(newBook)Replaces all current children with this child
replaceChildren()Removes all children
book.remove()Detaches the selected list item

script.js — select again after an update

This replacement script.js removes the first original book, replaces the remaining list children with one new book, then creates a fresh NodeList. Save, refresh, and inspect the Console.

javascript
const readingList = document.querySelector('#reading-list');
const book = document.querySelector('#reading-list .book');

if (readingList === null || book === null) {
  console.log('Required element was not found.');
} else {
  book.remove();

  const newBook = document.createElement('li');
  newBook.textContent = 'JavaScript Basics';
  newBook.classList.add('book');

  readingList.replaceChildren(newBook);

  const books = document.querySelectorAll('#reading-list .book');
  console.log(books.length);
}
Expected output
1

The fresh selection contains the one current `.book` element. The visible list contains only JavaScript Basics; the controls remain on the page.

Practice the add sequence

Order the add sequence

Put these actions in the order for adding one configured book item to the Reading List.

  1. Set its text content and add its base class.
  2. Create an `li` element.
  3. Insert the element as a child of `#reading-list`.

Apply the structural operations

Complete the list transformation

Starting with the unchanged Reading List page, complete the three structural method calls and the final message. Predict the title of the one item that remains after the operations finish.

javascript
const message = document.querySelector('#message');
const readingList = document.querySelector('#reading-list');
const bookTitle = document.querySelector('#book-title');
const book = document.querySelector('#reading-list .book');

if (message === null || readingList === null || bookTitle === null || book === null) {
  console.log('Required element was not found.');
} else {
  const newBook = document.createElement('li');

  newBook.textContent = bookTitle.value;
  newBook.classList.add('book');

  readingList.(newBook);
  book.();
  readingList.(newBook);
  message.textContent = ' remains.';
}

Step 9 of 9

Apply and Verify a Safe DOM Update

Complete a local previewBook() helper, run it with HTML-like input, and verify that the page updates safely without parsing supplied text as markup.

Plan one narrow DOM update

One focused update

You have already learned each DOM operation separately. Now combine them in one helper: previewBook().

Each time you call it, the helper selects the required elements, explicitly guards against a missing element, reads the controls as they are now, and creates one li. The typed string becomes text content, not page structure.

The update stays narrow: it replaces only #reading-list children, then updates the message, fixed list metadata, and Preview control state. There is no event listener yet; you will call the helper manually from the Console.

The safe update flow

Each operation has a narrow target, so the controls and surrounding page structure remain intact.

Use the API that matches the part of the DOM tree or control being changed.
NeedTargetOperation
Current typed titleText inputRead value property
New book itemNew li elementCreate, set text, add classes
Displayed listList children onlyReplace children
Status sentenceMessage paragraphReplace textContent
List metadataList attributeSet title attribute
Preview availabilityButton controlSet disabled property

Keep supplied text separate from structure

Diagram showing a current input value becoming a plain text node in a new list item, while separate updates affect the message, list attribute, and button property.

The input becomes text inside a new list item; it is not interpreted as markup.

Practice: finish the helper

Complete the integrated helper

Complete the helper in script.js. The helper selects its required elements when it is called, so the explicit guard can safely stop the update if any selection returns null.

Fill each blank with the operation that matches its target. The function is not connected to a click: after saving and refreshing, you can invoke it manually from the Console.

Practice: finish the helper

Fill the four DOM operations.

javascript
function previewBook() {
  const message = document.querySelector('#message');
  const readingList = document.querySelector('#reading-list');
  const bookTitle = document.querySelector('#book-title');
  const highlightBook = document.querySelector('#highlight-book');
  const previewButton = document.querySelector('#preview-button');

  if (message === null || readingList === null || bookTitle === null || highlightBook === null || previewButton === null) {
    return;
  }

  const newBook = document.createElement('li');
  newBook.textContent = bookTitle.value;
  newBook.classList.add('book');
  newBook.classList.toggle('', highlightBook.checked);

  readingList.replaceChildren(newBook);
  message. = 'Preview ready.';
  readingList.('title', 'Books to explore');
  previewButton. = false;
}

Run and inspect the local update

script.js

Replace the contents of script.js with this complete helper. Save the file and refresh your local page so the function is available in the Console.

javascript
function previewBook() {
  const message = document.querySelector('#message');
  const readingList = document.querySelector('#reading-list');
  const bookTitle = document.querySelector('#book-title');
  const highlightBook = document.querySelector('#highlight-book');
  const previewButton = document.querySelector('#preview-button');

  if (message === null || readingList === null || bookTitle === null || highlightBook === null || previewButton === null) {
    return;
  }

  const newBook = document.createElement('li');
  newBook.textContent = bookTitle.value;
  newBook.classList.add('book');
  newBook.classList.toggle('is-highlighted', highlightBook.checked);

  readingList.replaceChildren(newBook);
  message.textContent = 'Preview ready.';
  readingList.setAttribute('title', 'Books to explore');
  previewButton.disabled = false;
}

Run the safety check

In the page, replace the input value with this exact safety-test string. Leave Highlight new book checked. Without refreshing, enter previewBook() in the Console.

<strong>New book</strong> & notes

The list now has one preview item. Its visible content must include the angle brackets, tag name, and ampersand as ordinary characters. The input, checkbox, and button remain on the page because only the list’s children were replaced.

Verify the current page

Console — run these checks after calling previewBook(). The first line checks for a nested strong element using a selector; the last line checks whether an element with the highlight class was found.

javascript
console.log(document.querySelector('#reading-list strong'));
console.log(document.querySelector('#reading-list').getAttribute('title'));
console.log(document.querySelector('#preview-button').disabled);
console.log(document.querySelectorAll('#reading-list .book').length);
console.log(document.querySelector('#reading-list .is-highlighted') !== null);
Expected output
null
Books to explore
false
1
true

The final line is true when the checkbox remained checked before you invoked the helper.

Test the missing-element path

Test the guard without editing your saved files. First reload to restore the starter page. In the Console, remove the Preview control, then invoke the helper:

document.querySelector('#preview-button').remove();
previewBook();

One required selection is now null, so the helper returns without an error. The initial message and two original list items remain unchanged. Reload once more to restore the removed control.

Review and final verification

Summary

Choice guide

  • Use textContent when a string must become literal visible text.
  • Use getAttribute(), setAttribute(), and removeAttribute() for ordinary attributes such as title.
  • Use live properties such as value, checked, and disabled for current control state.
  • Use classList to preserve base classes while changing emphasis.
  • Use createElement(), replaceChildren(), and remove() for focused child-element changes.

Final application: verify the safe update

Run the completed helper locally with the exact safety-test string and a checked checkbox. Write a compact verification report that includes:

  1. The literal text visible in the preview item.
  2. The number and structure of .book items after the update.
  3. Whether the preview item received the highlight class.
  4. Whether Preview is available, and whether the other controls remained present.
  5. Your Console or inspector evidence about a nested strong element.
  6. What happened after you removed the required Preview control and invoked the helper.

Do not refresh between entering the safety-test string and calling previewBook().

Write at least 220 characters (0/220).

Tutorial completed

Congratulations! You completed: Read and Update the DOM Safely

Completed: you integrated and verified a safe local DOM update. Next, you will connect updates to user actions with browser event listeners.

Download the App now to have access to + 5000 free courses, exercises, certificates and lots of content without paying anything!

  • 100% free online courses from start to finish

    Thousands of online courses in video, ebooks and audiobooks.

  • More than 60 thousand free exercises

    To test your knowledge during online courses

  • Valid free Digital Certificate with QR Code

    Generated directly from your cell phone's photo gallery and sent to your email

Cursa app on the ebook screen, the video course screen and the course exercises screen, plus the course completion certificate