Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ <h1>TV Show Episodes</h1>

<!-- Controls -->
<div id="controls">
<!-- Show selector -->
<select id="showSelect">
<option value="">Select a show</option>
</select>
<!-- Search box-->
<input id="searchInput" placeholder="Search episodes..." />
<p id="resultsCount"></p>

<!-- Episode selector-->
<select id="episodeSelect">
<option value="">All Episodes</option>
</select>
Expand All @@ -28,4 +33,4 @@ <h1>TV Show Episodes</h1>

<script src="script.js"></script>
</body>
</html>
</html>
119 changes: 80 additions & 39 deletions script.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,94 @@

// Grab the elements we need from the page
const root = document.getElementById("root");
const searchInput = document.getElementById("searchInput");
const resultsCount = document.getElementById("resultsCount");
const episodeSelect = document.getElementById("episodeSelect");

// Holds all episodes after they are fetched
let allEpisodes = [];

// Cache fetched episode lists so we never fetch the same show twice
const episodeCache = {};

// Loading message

// Let the user know the page is loading
root.innerHTML = "<p style='text-align:center;'>Loading episodes... ⏳</p>";


// Fetch data (ONLY ONCE)

fetch("https://api.tvmaze.com/shows/82/episodes")
// Fetch all shows from TVMaze when the page first loads
fetch("https://api.tvmaze.com/shows")
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch");
}
return response.json();
})
.then((episodes) => {
allEpisodes = episodes;
.then((shows) => {
// Sort shows alphabetically, case-sensitive
shows.sort((a, b) =>
a.name.toLowerCase().localeCompare(b.name.toLowerCase()),
);

populateDropdown(allEpisodes);
displayEpisodes(allEpisodes);
// Add each show as an option in the show selector
shows.forEach((show) => {
const option = document.createElement("option");
option.value = show.id;
option.textContent = show.name;
showSelect.appendChild(option);
});
root.innerHTML =
"<p class='loading-message'>Select a show to get started</p>";
})
.catch((error) => {
root.innerHTML = `
<p style="color:red; text-align:center;">
❌ Failed to load episodes. Please try again later.
</p>
`;
// If something goes wrong, show a message on the page
root.innerHTML =
"<p class='error-message'>❌ Something went wrong loading shows. Please try again later.</p>";
console.error(error);
});

// Fetch episodes for the selected show when the user makes a selection
showSelect.addEventListener("change", () => {
const selectedShowId = showSelect.value;

if (!selectedShowId) return;

// Show a loading message while episodes are being fetched
root.innerHTML =
"<p class='loading-message'>Loading episodes... please wait</p>";

// Use cached episodes if we have already fetched this show
if (episodeCache[selectedShowId]) {
allEpisodes = episodeCache[selectedShowId];
searchInput.value = "";
populateDropdown(allEpisodes);
displayEpisodes(allEpisodes);
return;
}

// Search
fetch(`https://api.tvmaze.com/shows/${selectedShowId}/episodes`)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch episodes");
}
return response.json();
})
.then((episodes) => {
// Save to cache so we don't fetch this show again
episodeCache[selectedShowId] = episodes;
allEpisodes = episodes;
// Reset search for the new show
searchInput.value = "";
// Fill in the dropdown and show all episodes on first load
populateDropdown(allEpisodes);
displayEpisodes(allEpisodes);
})
.catch((error) => {
// If something goes wrong, show a message on the page instead of the console
root.innerHTML =
"<p class= 'error message'>❌ Failed to load episodes. Please try again later.</p>";
console.error(error);
});
});

// |Re-filter the episode list each time the user types in the search box
searchInput.addEventListener("input", () => {
const searchTerm = searchInput.value.toLowerCase();

Expand All @@ -52,14 +102,11 @@ searchInput.addEventListener("input", () => {
displayEpisodes(filtered);
});


// Display episodes

// Clear the grid and render a fresh set of episode cards
function displayEpisodes(episodes) {
root.innerHTML = "";

resultsCount.textContent =
`Displaying ${episodes.length} / ${allEpisodes.length} episodes`;
resultsCount.textContent = `Displaying ${episodes.length} / ${allEpisodes.length} episodes`;

episodes.forEach((ep) => {
const code = formatEpisodeCode(ep.season, ep.number);
Expand All @@ -68,6 +115,7 @@ function displayEpisodes(episodes) {
div.className = "episode";
div.id = `episode-${ep.id}`;

// Remove any HTML tags from the summary text before rendering
div.innerHTML = `
<h3>${code} - ${ep.name}</h3>
<img src="${ep.image?.medium || ""}" alt="${ep.name}">
Expand All @@ -78,9 +126,7 @@ function displayEpisodes(episodes) {
});
}


// Dropdown

// Add every episode as an option in the dropdown menu
function populateDropdown(episodes) {
episodeSelect.innerHTML = `<option value="">All Episodes</option>`;

Expand All @@ -95,32 +141,27 @@ function populateDropdown(episodes) {
});
}


// Jump to episode

// Show only selected episode, or all episodes if All Episodes is chosen
episodeSelect.addEventListener("change", () => {
const selectedId = episodeSelect.value;

if (!selectedId) {
// Reset to showing all episodes
displayEpisodes(allEpisodes);
return;
}

// Find the matching episode and display only that one
const selectedEpisode = allEpisodes.filter(
(ep) => ep.id === Number(selectedId),
);
// Ensure all episodes are visible before scrolling
displayEpisodes(allEpisodes);

const element = document.getElementById(`episode-${selectedId}`);

if (element) {
element.scrollIntoView({ behavior: "smooth" });
}
displayEpisodes(selectedEpisode);
});


// Format SxxExx

// Format season and episode numbers into SxxExx
function formatEpisodeCode(season, number) {
const s = String(season).padStart(2, "0");
const e = String(number).padStart(2, "0");
return `S${s}E${e}`;
}
}
37 changes: 35 additions & 2 deletions style.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ h1 {
border-radius: 12px;
overflow: hidden;
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
transition: transform 0.2s ease, box-shadow 0.2s ease;
transition:
transform 0.2s ease,
box-shadow 0.2s ease;
}

.episode:hover {
Expand All @@ -84,6 +86,37 @@ h1 {
line-height: 1.4;
}

/* Loading message */
.loading-message {
text-align: center;
color: #ffcccc;
font-size: 1rem;
padding: 20px;
}

/* Error message */
.error-message {
text-align: center;
color: #ff6666;
font-size: 1rem;
padding: 20px;
}

/* Show selector */
#showSelect {
display: block;
margin: 10px auto;
padding: 10px 15px;
width: 50%;
max-width: 400px;
border: none;
border-radius: 8px;
font-size: 1rem;
background: #1a0000;
color: #fff;
outline: none;
}

/* Footer */
footer {
text-align: center;
Expand All @@ -98,4 +131,4 @@ footer a {

footer a:hover {
text-decoration: underline;
}
}