Separating content from code — JSON structure, local data files, the fetch() function, Promises, rendering data as HTML, and handling loading and error states
Every website you have built so far has contained content that was written directly into HTML files. The text, the links, the images, the form labels — all of it lived inside the HTML you typed by hand. This approach works well for small sites. But many modern websites display content that changes often: news articles, product listings, user profiles, event schedules, blog posts, resource directories. Writing that content by hand into HTML every time something changes would be tedious, error-prone, and difficult to maintain.
There is a better approach. Websites can store content separately from the HTML that displays it, in a format called JSON (JavaScript Object Notation). JavaScript can then read that content and build the page dynamically. For content that lives on a remote server, JavaScript can fetch it over the network. This chapter introduces both JSON and fetching at a beginner level — vocabulary and habits that connect directly to later courses in JavaScript, front-end frameworks, APIs, databases, and full-stack development.
A resource directory page needs to display eight learning resources today. Next year it might need to display two hundred. A teammate wants to add a new resource without touching a single line of HTML. A visitor on a slow connection should never see a blank, unexplained gap on the page while content loads — and if the data fails to arrive at all, the page should still make sense. How do you build a page like that? The answer is to separate the content from the page that displays it, and to teach JavaScript how to load that content, render it, and respond gracefully when something goes wrong. That is exactly what this chapter teaches.
By the end of this chapter, you should be able to:
fetch() requires a local development server rather than a bare file path.fetch() works and why network requests in JavaScript are asynchronous..then() and .catch() handle results and errors.Open any large website and look carefully at what you see. A news site has dozens of article summaries, each with a headline, a photo, a category, and a short description. An online store has hundreds of products, each with a name, a price, an image, and a stock status. A school website has a directory of staff, each with a name, a title, a department, and a photo.
If each of those items were written manually into HTML, maintaining the site would be a continuous struggle. Adding a new article would mean opening the HTML file, writing the markup, making sure the structure matches every other article, saving, uploading, and testing. Changing a price would mean hunting through the HTML for the right line. Removing a staff member who left would mean finding every page that mentioned them.
Instead, most websites separate content from presentation. Content lives in a structured data format. Presentation logic lives in HTML, CSS, and JavaScript templates. When the page loads, JavaScript reads the data and builds the HTML automatically. Changing a product price means changing a number in a data file, not editing HTML.
This separation of data from presentation is one of the most important architectural ideas in web development. Even at a beginner level, learning to work with separate data makes your websites easier to update, extend, and eventually connect to real databases and APIs.
When you think about a website as data-driven, you start asking a different set of questions:
These questions guide the work of this entire chapter.
JSON stands for JavaScript Object Notation. It is a lightweight text format for storing and exchanging structured data. JSON looks similar to JavaScript objects and arrays, which makes it easy to read and write for anyone who has worked through the earlier chapters of this textbook.
JSON was designed to be readable by humans and easy to parse by machines. It is now the most widely used data format on the web. When one website or application sends data to another — through an API, a database query, or a file — JSON is usually the format used.
JSON files typically have a .json file extension. They are plain text files, which means you can open them in any text editor. They contain no HTML, no CSS, and no JavaScript — just structured data. Here is a simple example of JSON representing a single person:
{
"name": "Amara Osei",
"role": "Student",
"year": 2,
"active": true
}
This should look familiar. The structure uses curly braces, property names in double quotes, a colon between each name and value, and commas between properties. This is the same syntax as a JavaScript object. There are a few important rules for valid JSON:
// INVALID JSON — do not do this
{
name: "Amara Osei",
role: 'Student',
year: 2,
active: true,
}
{
"name": "Amara Osei",
"role": "Student",
"year": 2,
"active": true
}
Unlike JavaScript, which is flexible about many syntax issues, JSON is strict. A single missing double quote, a trailing comma, or an unquoted property name will cause a JSON parse error. This strictness is intentional: it keeps JSON simple and consistent across many different programming languages, not just JavaScript. When you write JSON by hand, always validate it. There are free online JSON validators you can paste your JSON into to check for errors.
JSON supports two main structures: objects and arrays. These can be nested inside each other in any combination.
A JSON object is a collection of key-value pairs. Keys are strings. Values can be any valid JSON value. Objects are wrapped in curly braces.
{
"title": "Introduction to HTML",
"chapter": 5,
"published": true,
"author": "MGU Editorial Team"
}
You can read this as: there is an object with four properties — title, chapter, published, and author.
A JSON array is an ordered list of values. Arrays are wrapped in square brackets. Each item is separated by a comma. Array items can be any valid JSON value — strings, numbers, Booleans, objects, or even other arrays.
[
"HTML",
"CSS",
"JavaScript"
]
The most common structure you will encounter is an array of objects. This represents a list of similar items, where each item has the same set of properties.
[
{
"id": 1,
"title": "What Web Development Studies",
"chapter": 1,
"part": "Foundations of the Web"
},
{
"id": 2,
"title": "How the Web Works",
"chapter": 2,
"part": "Foundations of the Web"
},
{
"id": 3,
"title": "Files, Folders, Paths, and Project Structure",
"chapter": 3,
"part": "Foundations of the Web"
}
]
This structure is extremely useful for pages like resource directories, product listings, team member rosters, event schedules, and course guides — anywhere you have a repeating collection of similar items.
Objects can contain other objects as property values.
{
"name": "Kofi Mensah",
"contact": {
"email": "[email protected]",
"phone": "555-0102"
},
"department": "Computer Science"
}
Here, the contact property is itself an object with email and phone properties. To access email in JavaScript after parsing this JSON, you would write person.contact.email.
Before writing any code, practice reading JSON by answering these questions about a data structure: Is the top-level structure an object {} or an array []? If it is an array, what does each item look like? What are the property names? What type is each value — string, number, Boolean, array, or nested object? These reading habits will serve you well as JSON structures grow more complex.
Before learning to fetch data from a remote server, you will work with local JSON files. A local JSON file lives inside your project folder, just like your CSS or JavaScript files.
Using a local JSON file is good for learning because it removes the complexity of network requests. You can focus on reading and rendering data without worrying about server errors, network delays, or CORS policies — concepts you will encounter when working with remote APIs in later courses.
In your project folder, create a data folder. Inside it, create a file called resources.json.
Here is sample content for resources.json:
[
{
"id": 1,
"title": "MDN Web Docs: HTML",
"category": "HTML",
"description": "Mozilla's comprehensive reference for HTML elements, attributes, and best practices.",
"url": "https://developer.mozilla.org/en-US/docs/Web/HTML"
},
{
"id": 2,
"title": "MDN Web Docs: CSS",
"category": "CSS",
"description": "Mozilla's complete CSS reference including properties, selectors, and layout guides.",
"url": "https://developer.mozilla.org/en-US/docs/Web/CSS"
},
{
"id": 3,
"title": "MDN Web Docs: JavaScript",
"category": "JavaScript",
"description": "Mozilla's JavaScript reference covering syntax, built-in objects, and browser APIs.",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript"
},
{
"id": 4,
"title": "The A11Y Project",
"category": "Accessibility",
"description": "A community-driven effort to make digital accessibility easier to understand and practice.",
"url": "https://www.a11yproject.com"
},
{
"id": 5,
"title": "CSS-Tricks: Flexbox Guide",
"category": "CSS",
"description": "A thorough visual guide to CSS Flexbox with diagrams and interactive examples.",
"url": "https://css-tricks.com/snippets/css/a-guide-to-flexbox/"
}
]
This file defines an array of five resource objects. Each has an id, a title, a category, a description, and a url.
Later in this chapter, you will use fetch() to read this file. One important thing to understand early: fetch() uses HTTP to request files, which means your page must be served by a web server — even locally. You cannot simply open an HTML file by double-clicking it in your file manager and expect fetch() to load a local JSON file. Modern browsers block this for security reasons.
To work with fetch() locally, use a simple local development server. Most code editors, including Visual Studio Code with the Live Server extension, provide this. When your page is served at an address like http://localhost:5500 instead of accessed as a bare file path, fetch() will work correctly. This requirement is not a bug — it is consistent with how websites actually work. Your project runs locally on a small server, just as it would run on a real hosting server when published.
The fetch() function is the browser's built-in tool for making network requests from JavaScript. It can request HTML, JSON, images, or any other resource from a URL. At its most basic, fetch() takes a URL as its argument and returns a response.
fetch("data/resources.json");
However, this line alone does nothing visible. It sends the request but does not do anything with the response. To work with the response, you need to understand something about how fetch() works: it is asynchronous.
When a browser sends a network request, it does not freeze and wait for the response before continuing with other work. Waiting could take milliseconds or several seconds depending on server speed, file size, and network conditions. If the browser stopped everything while waiting, the page would become unresponsive.
Instead, the browser sends the request and continues running other JavaScript. When the response arrives, a callback function handles it. This is asynchronous behavior: the request and the response happen at different times, and JavaScript continues working in between. This is different from the synchronous code you have written before, where each line executes before the next one begins.
fetch() handles this through Promises, which are introduced in the next section.
Here is a complete, working example of using fetch() to load and log a JSON file. Read it carefully before studying the explanation below.
fetch("data/resources.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data);
})
.catch(function(error) {
console.log("Error loading data:", error);
});
If you open the browser console after loading the page, you should see the parsed array logged there.
A Promise is a JavaScript object that represents the eventual result of an asynchronous operation. It is called a Promise because it represents a commitment: the result is not here yet, but it will be here eventually — or the operation will fail and the Promise will tell you that too.
You work with Promises using the .then() and .catch() methods. .then(callback) runs the callback function when the Promise is fulfilled; the callback receives the result value as its argument. .catch(callback) runs the callback function if the Promise is rejected at any point in the chain; the callback receives the error as its argument.
Promises can be chained. When a .then() callback returns another Promise, the next .then() in the chain waits for that inner Promise to resolve. This is exactly what happens with fetch():
fetch("data/resources.json") // Returns a Promise for a Response
.then(function(response) {
return response.json(); // Returns a Promise for parsed JSON
})
.then(function(data) {
// data is the fully parsed JSON value
renderResources(data);
})
.catch(function(error) {
showError(error);
});
The chain reads in order: first fetch, then parse, then use the data. If anything fails, catch the error.
Think of a Promise like placing an order at a restaurant. The moment you order, you receive a ticket (the Promise). The food is not ready yet, but the ticket represents the commitment that food is coming. You do not stand frozen at the counter — you go sit down and wait (the browser continues running other JavaScript). When the food is ready, the waiter brings it to your table and you eat (the .then() callback runs with the result). If the kitchen runs out of ingredients, the waiter comes to tell you there is a problem (the .catch() callback runs with the error).
A newer way to write the same logic uses the async and await keywords. You may see this style in documentation and other codebases.
async function loadResources() {
try {
const response = await fetch("data/resources.json");
const data = await response.json();
renderResources(data);
} catch (error) {
showError(error);
}
}
loadResources();
async before a function declaration marks the function as asynchronous. Inside an async function, await pauses execution of that function — not the entire page — until the Promise resolves, and then returns the resolved value. try/catch handles errors in place of .catch().
Both the .then()/.catch() style and the async/await style accomplish the same thing. This textbook uses the .then()/.catch() style in primary examples, since it makes the Promise structure more visible, but shows the async/await style as well so you can read it when you encounter it elsewhere.
Loading data and logging it to the console is only the beginning. The real goal is to display that data as visible HTML on the page. The process follows a clear pattern:
Create a container element in your HTML where the rendered content will appear.
<section id="resources-section">
<h2>Web Development Resources</h2>
<div id="resources-list"></div>
</section>
The <div id="resources-list"> is intentionally empty. JavaScript will fill it.
In your JavaScript file, write a function that receives the data array and renders it into the container.
function renderResources(resources) {
const container = document.querySelector("#resources-list");
let html = "";
for (let i = 0; i < resources.length; i++) {
const resource = resources[i];
html += `
<div class="resource-card">
<h3>${resource.title}</h3>
<span class="category">${resource.category}</span>
<p>${resource.description}</p>
<a href="${resource.url}" target="_blank" rel="noopener noreferrer">
Visit Resource
</a>
</div>
`;
}
container.innerHTML = html;
}
fetch("data/resources.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
renderResources(data);
})
.catch(function(error) {
console.log("Could not load resources:", error);
});
When the page loads, JavaScript requests the JSON file, parses it, and passes the array to renderResources(), which builds and inserts the HTML. The result is a page that displays all five resources as cards, built entirely from data.
Arrays in JavaScript have a built-in forEach method that runs a function once for each item. Many developers prefer it over an index-based for loop for this kind of work.
function renderResources(resources) {
const container = document.querySelector("#resources-list");
let html = "";
resources.forEach(function(resource) {
html += `
<div class="resource-card">
<h3>${resource.title}</h3>
<span class="category">${resource.category}</span>
<p>${resource.description}</p>
<a href="${resource.url}" target="_blank" rel="noopener noreferrer">
Visit Resource
</a>
</div>
`;
});
container.innerHTML = html;
}
The behavior is identical to the for loop version. resources.forEach(function(resource) { ... }) passes each item in the array to the callback function as the resource parameter.
When a page makes a network request, there is a period of time between when the request is sent and when the data arrives. During this time, the container is empty. If a user loads the page on a slow connection, they might see a blank area for several seconds with no explanation. A good user experience shows a loading state during this period — some kind of message or visual indicator that data is on its way.
Set a loading message in the container before the fetch() call begins.
const container = document.querySelector("#resources-list");
// Show loading message before the fetch begins
container.innerHTML = "<p class='loading-message'>Loading resources...</p>";
fetch("data/resources.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
renderResources(data); // This replaces the loading message
})
.catch(function(error) {
container.innerHTML = "<p class='error-message'>Could not load resources. Please try again later.</p>";
});
The loading message is inserted before the fetch starts. When renderResources(data) runs, it replaces container.innerHTML, clearing the loading message. If an error occurs, the catch block replaces the loading message with an error message instead.
Add a role="status" attribute to the loading message so that screen readers announce it:
<p class="loading-message" role="status">Loading resources...</p>
You can add this attribute in JavaScript when building the loading message string. This helps users who rely on screen readers know that something is happening on the page, even though they cannot see it. Some websites show animated spinners or skeleton screens during loading — these are CSS-based techniques. For this textbook, a simple text message is appropriate; more advanced loading UI patterns belong in later front-end development courses.
Network requests can fail for many reasons: the server is unreachable, the file does not exist, the JSON is malformed, the user's connection is offline. A well-built page handles these failures gracefully instead of silently showing a blank container.
A .catch() block runs when the request itself fails — for example, a network error or a DNS failure. It does not automatically run for HTTP error responses like 404 (Not Found) or 500 (Server Error). When a server returns a 404, fetch() still considers the request a technical success and does not reject the Promise.
To handle HTTP errors properly, check the response.ok property, which is true for status codes in the 200–299 range and false for anything else.
fetch("data/resources.json")
.then(function(response) {
if (!response.ok) {
throw new Error("Server returned an error: " + response.status);
}
return response.json();
})
.then(function(data) {
renderResources(data);
})
.catch(function(error) {
showError(error.message);
});
if (!response.ok) checks whether the HTTP status code indicates success. If the server returns a 404 or 500, response.ok is false. throw new Error(...) forces the Promise chain into the rejection state, triggering the .catch() block with a descriptive message.
function showError(message) {
const container = document.querySelector("#resources-list");
container.innerHTML = `
<p class="error-message" role="alert">
We were unable to load the resources at this time. Please try refreshing the page.
</p>
`;
console.error("Fetch error:", message);
}
Note the role="alert" attribute. This tells screen readers to announce the message immediately when it appears, which is appropriate for error conditions. Log the technical error to the console for debugging purposes. Do not show raw technical error messages to users — they are rarely helpful and can sometimes expose information about your site's internal structure.
Planning for all three states is a professional habit. Pages that only handle the success case will show users blank or broken content whenever anything goes wrong.
So far, this chapter has used a local JSON file. In real projects, you will often fetch data from external APIs — services provided by other organizations that return data over the web. Before doing so, there are important practices and principles to understand.
An API (Application Programming Interface) is a structured way for one software system to communicate with another. A web API accepts HTTP requests and returns data, usually in JSON format. For example, a weather service might offer an API endpoint at a URL like https://api.example-weather.com/forecast?city=Accra. Your JavaScript sends a request to that URL, and the weather service responds with JSON containing temperature, conditions, and forecast data.
Working with real external APIs is a topic for later courses in JavaScript and full-stack development. This chapter focuses on the patterns for reading and rendering data, which are the same whether the data comes from a local file or a remote API.
| Consideration | What to Do |
|---|---|
| Attribution and terms of service | External data sources are owned by people and organizations. Before using data from an external source, read its terms of service. Some data is freely available for any use; some requires attribution; some prohibits commercial use; some requires an API key. For public data you use on your website, attribute the source clearly — this is good practice even when not strictly required. |
| Sanitizing untrusted data | When you insert fetched data into the page using innerHTML, you are trusting that the data does not contain malicious HTML or scripts. For data you control, this is not a concern. For data from third-party sources, treat the content carefully and consider using textContent instead of innerHTML for plain text. |
| Sensitive or personal data | Some APIs return personal data — names, email addresses, phone numbers. Handle this data responsibly. Do not store it beyond what is necessary, and be especially careful about logging personal data to the console in production code. |
| Rate limits and fair use | Many external APIs enforce rate limits — restrictions on how many requests you can make in a given time period. Sending too many requests can get your IP address blocked or your API key revoked. Later courses introduce strategies like caching to reduce unnecessary calls. |
// Safer for plain text content that should not contain HTML
const titleElement = document.createElement("h3");
titleElement.textContent = resource.title; // Not innerHTML
Using textContent means any HTML-like characters in the string are treated as plain text, not as markup. For beginner projects using your own controlled data, innerHTML with template literals is fine. As you work with more sources, developing the habit of distinguishing between markup you build and data you receive is important.
This worked example brings together every skill from this chapter. You will build a resource directory page that loads resources from a local JSON file and displays them as filterable cards.
[
{
"id": 1,
"title": "MDN Web Docs: HTML",
"category": "HTML",
"description": "Mozilla's comprehensive reference for HTML elements, attributes, and best practices.",
"url": "https://developer.mozilla.org/en-US/docs/Web/HTML"
},
{
"id": 2,
"title": "MDN Web Docs: CSS",
"category": "CSS",
"description": "Mozilla's complete CSS reference including properties, selectors, and layout guides.",
"url": "https://developer.mozilla.org/en-US/docs/Web/CSS"
},
{
"id": 3,
"title": "MDN Web Docs: JavaScript",
"category": "JavaScript",
"description": "Mozilla's JavaScript reference covering syntax, built-in objects, and browser APIs.",
"url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript"
},
{
"id": 4,
"title": "The A11Y Project",
"category": "Accessibility",
"description": "A community-driven effort to make digital accessibility easier to understand and practice.",
"url": "https://www.a11yproject.com"
},
{
"id": 5,
"title": "CSS-Tricks: Flexbox Guide",
"category": "CSS",
"description": "A thorough visual guide to CSS Flexbox with diagrams and interactive examples.",
"url": "https://css-tricks.com/snippets/css/a-guide-to-flexbox/"
},
{
"id": 6,
"title": "JavaScript.info",
"category": "JavaScript",
"description": "A modern JavaScript tutorial covering the language from basics to advanced topics.",
"url": "https://javascript.info"
},
{
"id": 7,
"title": "web.dev: Responsive Design",
"category": "CSS",
"description": "Google's guide to building responsive layouts for different screen sizes.",
"url": "https://web.dev/learn/design"
},
{
"id": 8,
"title": "WebAIM: Color Contrast Checker",
"category": "Accessibility",
"description": "An online tool for checking whether text and background color combinations meet accessibility standards.",
"url": "https://webaim.org/resources/contrastchecker/"
}
]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Development Resources</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<header>
<nav>
<a href="index.html">Home</a>
<a href="about.html">About</a>
<a href="resources.html" aria-current="page">Resources</a>
<a href="contact.html">Contact</a>
</nav>
</header>
<main>
<h1>Web Development Resources</h1>
<p>A curated list of resources for learning HTML, CSS, JavaScript, and accessibility.</p>
<div id="filter-controls" role="group" aria-label="Filter resources by category">
<button class="filter-btn active" data-category="all">All</button>
<button class="filter-btn" data-category="HTML">HTML</button>
<button class="filter-btn" data-category="CSS">CSS</button>
<button class="filter-btn" data-category="JavaScript">JavaScript</button>
<button class="filter-btn" data-category="Accessibility">Accessibility</button>
</div>
<div id="resources-list" aria-live="polite"></div>
</main>
<footer>
<p>© 2025 Student Learning Site</p>
</footer>
<script src="js/resources.js"></script>
</body>
</html>
Note the aria-live="polite" attribute on #resources-list. This tells screen readers to announce changes to this region when they occur — useful when filtering updates the content.
// Store all resources for filtering
let allResources = [];
// Select the container
const container = document.querySelector("#resources-list");
// Show loading state
container.innerHTML = "<p class='loading-message' role='status'>Loading resources...</p>";
// Fetch the data
fetch("data/resources.json")
.then(function(response) {
if (!response.ok) {
throw new Error("Network response was not OK: " + response.status);
}
return response.json();
})
.then(function(data) {
allResources = data;
renderResources(allResources);
setupFilters();
})
.catch(function(error) {
container.innerHTML = `
<p class="error-message" role="alert">
Resources could not be loaded. Please try refreshing the page.
</p>
`;
console.error("Fetch error:", error.message);
});
// Build and insert cards for a given array of resources
function renderResources(resources) {
if (resources.length === 0) {
container.innerHTML = "<p class='no-results'>No resources found for this category.</p>";
return;
}
let html = "";
resources.forEach(function(resource) {
html += `
<div class="resource-card">
<div class="card-header">
<h2>${resource.title}</h2>
<span class="category-badge">${resource.category}</span>
</div>
<p class="card-description">${resource.description}</p>
<a class="card-link" href="${resource.url}" target="_blank" rel="noopener noreferrer">
Visit Resource <span aria-hidden="true">→</span>
</a>
</div>
`;
});
container.innerHTML = html;
}
// Set up filter buttons
function setupFilters() {
const buttons = document.querySelectorAll(".filter-btn");
buttons.forEach(function(button) {
button.addEventListener("click", function() {
// Update active button styling
buttons.forEach(function(btn) {
btn.classList.remove("active");
btn.removeAttribute("aria-pressed");
});
button.classList.add("active");
button.setAttribute("aria-pressed", "true");
// Filter and render
const category = button.getAttribute("data-category");
if (category === "all") {
renderResources(allResources);
} else {
const filtered = allResources.filter(function(resource) {
return resource.category === category;
});
renderResources(filtered);
}
});
});
}
In css/main.css, add styles for the resource cards and filter buttons:
#filter-controls {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 2rem;
}
.filter-btn {
padding: 0.5rem 1rem;
border: 2px solid var(--color-primary);
background: transparent;
color: var(--color-primary);
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
}
.filter-btn.active,
.filter-btn:hover {
background: var(--color-primary);
color: white;
}
#resources-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
.resource-card {
border: 1px solid var(--color-border);
border-radius: 8px;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 0.5rem;
}
.card-header h2 {
font-size: 1rem;
margin: 0;
}
.category-badge {
font-size: 0.75rem;
padding: 0.2rem 0.6rem;
background: var(--color-accent-light);
color: var(--color-accent);
border-radius: 12px;
white-space: nowrap;
}
.card-description {
font-size: 0.9rem;
line-height: 1.6;
color: var(--color-text-secondary);
}
.card-link {
margin-top: auto;
font-size: 0.9rem;
font-weight: 600;
}
.loading-message,
.error-message,
.no-results {
padding: 2rem;
text-align: center;
font-size: 1rem;
}
.error-message {
color: var(--color-error);
}
This worked example uses every major concept from the chapter: a local JSON file with an array of objects, fetch() with response.ok checking, a loading state before the fetch, an error state in the catch block, a renderResources() function that builds HTML with a forEach loop, a filter system that uses Array.filter() to select a subset of the loaded data, and accessibility attributes on interactive regions and buttons.
Answer these questions before beginning the full review questions. They check basic understanding, not deep analysis.
Read each code block carefully before answering the questions that follow it.
[
{
"id": 1,
"name": "Intro to HTML",
"type": "lesson",
"completed": true
},
{
"id": 2,
"name": "CSS Box Model",
"type": "lesson",
"completed": false
},
{
"id": 3,
"name": "HTML Practice Quiz",
"type": "quiz",
"completed": true
}
]
fetch("data/lessons.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log(data.length);
})
.catch(function(error) {
console.log("Error:", error);
});
function renderLessons(lessons) {
const list = document.querySelector("#lesson-list");
let html = "";
lessons.forEach(function(lesson) {
html += `<li class="${lesson.completed ? 'done' : 'pending'}">${lesson.name}</li>`;
});
list.innerHTML = "<ul>" + html + "</ul>";
}
Trace through the following code manually. At each step, write what the current value of the variable or expression is.
const resources = [
{ id: 1, title: "MDN HTML", category: "HTML" },
{ id: 2, title: "MDN CSS", category: "CSS" },
{ id: 3, title: "MDN JS", category: "JavaScript" },
{ id: 4, title: "A11Y Project", category: "Accessibility" },
{ id: 5, title: "CSS-Tricks Flexbox", category: "CSS" }
];
const cssOnly = resources.filter(function(item) {
return item.category === "CSS";
});
console.log(cssOnly.length);
console.log(cssOnly[0].title);
console.log(cssOnly[1].title);
For each scenario, predict what the user will see or what will happen.
{
"name": "Lesson One",
"order": 1,
"tags": ["beginner", "html"],
}
Each item below contains an error or problem. Identify it and describe how to fix it.
{
'title': 'Intro to CSS',
'chapter': 11,
'published': true
}
fetch("data/resources.json")
.then(function(response) {
return response.json;
})
.then(function(data) {
renderResources(data);
});
fetch("data/resources.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
renderResources(data);
});
function renderResources(resources) {
const container = document.querySelector("#resource-list");
resources.forEach(function(resource) {
container.innerHTML += `<div>${resource.tittle}</div>`;
});
}
fetch("data/items.json")
.then(function(response) {
return response.json();
})
.then(function(data) {
renderItems(data);
});
<div id="items-container"></div>
function renderItems(items) {
const container = document.querySelector("#item-container");
let html = "";
items.forEach(function(item) {
html += `<p>${item.name}</p>`;
});
container.innerHTML = html;
}
Build a complete, working resource directory page using a local JSON file.
JSON file: Create data/resources.json with at least eight resource objects. Each resource must have id, title, category, description, and url properties. Include at least three different category values.
HTML page: resources.html with a proper page structure including <header>, <main>, and <footer>, a heading and introductory paragraph, a row of filter buttons (one per category plus an "All" button), an empty container div with an appropriate id, and the JavaScript file linked before </body>.
JavaScript: Load the JSON with fetch(), display a loading message before the request, handle response.ok and throw an error for non-success responses, render the resources as cards in the container, show an error message if the fetch fails, implement filter buttons that re-render a subset of the data, and keep the full data array accessible so the "All" button restores the complete list.
CSS: Style the cards in a responsive grid using CSS Grid or Flexbox, style the filter buttons with a clear active state, style the loading and error messages appropriately, and make the layout responsive so cards stack properly on small screens.
Accessibility: Include aria-live="polite" on the resources container, use role="status" on the loading message, use role="alert" on the error message, use aria-pressed on filter buttons to indicate the active state, and ensure all links have meaningful text.
Review the worked example from this chapter and answer the following questions.
These questions draw on skills from earlier chapters.
Chapter 28 taught you how to separate content from code and bring it to life with fetch(), Promises, and rendered HTML. Chapter 29 consolidates the quality practices introduced throughout this textbook into a comprehensive review and audit process. You will learn systematic approaches to checking semantic structure, keyboard navigation, color contrast, alt text, form labels, image optimization, HTML validation, CSS debugging, JavaScript debugging, browser testing, and quality checklists. Chapter 29 treats accessibility and performance not as finishing touches but as fundamental requirements of any public website — and the skills in that chapter apply directly to the data-driven resource directory you built in this chapter.