-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
352 lines (317 loc) · 9.82 KB
/
script.js
File metadata and controls
352 lines (317 loc) · 9.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// script.js
/**
* @fileoverview Main application logic for the Pokédex web app.
*
* This file handles initial setup, loading and rendering of Pokémon cards,
* search functionality, event handling, and interaction with the PokéAPI.
*/
let matches = [];
let currentPokemon;
let startPokemon = 1;
let endPokemon = 21;
let searchPool;
const missingno = {
name: "missingNo.",
url: "https://en.wikipedia.org/wiki/MissingNo.",
};
const typeColors = {
normal: "#919AA2",
fighting: "#CE416B",
flying: "#8FA9DE",
poison: "#AA6BC8",
ground: "#D97845",
rock: "#C5B78C",
bug: "#91C12F",
ghost: "#5269AD",
steel: "#5A8EA2",
fire: "#FF9D54",
water: "#5090D6",
grass: "#63BC5A",
electric: "#F4D23C",
psychic: "#FA7179",
ice: "#73CEC0",
dragon: "#0B6DC3",
dark: "#5A5465",
fairy: "#EC8FE6",
unknown: "#FFFFFF",
};
/**
* Initializes the application on page load.
*
* Shows the loading screen, renders the initial batch of Pokémon,
* and prepares the search pool for user input.
*
* @returns {Promise<void>}
*/
async function init() {
showLoadingScreen();
await renderOverview(startPokemon, endPokemon);
loadSearchPool();
}
/**
* Fetches data for a specific Pokémon by ID from the PokéAPI.
*
* @param {number} id - The ID of the Pokémon to fetch.
* @returns {Promise<object>} A promise that resolves to the Pokémon data.
*/
async function getPokemonData(id) {
let url = `https://pokeapi.co/api/v2/pokemon/${id}`;
try {
let response = await fetch(url);
currentPokemon = await response.json();
return currentPokemon;
} catch (error) {
console.error(error);
}
}
/**
* Renders the MissingNo. fallback view when a Pokémon or its data could not be found.
*
* This function replaces the overview content with a MissingNo. template
* and adjusts the UI by hiding or showing relevant elements.
*/
function renderMissingNo() {
let container = getElementHelper("overview-container");
container.innerHTML = renderMissingNoTemplate();
hideLoadBtn();
showBackBtn();
hideLoadingScreen();
emptySearchInput();
disableSearchBtn();
}
/**
* Renders an overview of multiple Pokémon by ID range.
*
* This function fetches and renders individual Pokémon cards from start to end ID.
* It also includes a condition to hide the load button when the 151st Pokémon is rendered.
*
* @param {number} startPokemon - The starting Pokémon ID.
* @param {number} endPokemon - The ending Pokémon ID.
*/
async function renderOverview(startPokemon, endPokemon) {
let container = getElementHelper("overview-container");
for (let id = startPokemon; id <= endPokemon; id++) {
await renderOverviewSingleCard(id, container);
}
hideLoadingScreen();
}
/**
* Renders a single Pokémon overview card into the given container element.
*
* @param {number} id - The ID of the Pokémon to render.
* @param {HTMLElement} container - The HTML element where the card will be appended.
* @returns {Promise<void>}
*/
async function renderOverviewSingleCard(id, container) {
currentPokemon = await getPokemonData(id);
const backgroundColor = typeColors[currentPokemon.types[0].type.name] || "#66aed7";
try {
container.innerHTML += renderOverviewTemplate(currentPokemon, backgroundColor);
} catch (error) {
console.error(error);
renderMissingNo();
return;
}
renderTypes(`types-container-${currentPokemon.id}`);
if (currentPokemon.id >= 151) {
hideLoadBtn();
}
}
/**
* Renders one or two type icons of the current Pokémon into the given container.
*
* Works for both overview cards and detail views.
*
* @param {string} containerId - The ID of the HTML container element to render the types into.
*/
function renderTypes(containerId) {
let container = getElementHelper(containerId);
container.innerHTML = "";
for (let i = 0; i < currentPokemon.types.length; i++) {
container.innerHTML += renderTypesTemplate(i);
}
}
/**
* Starts the rendering process to load more Pokémon into the overview.
*
* Increases the current start and end range, while ensuring it stays within the bounds
* of the 1st generation (max ID 151). Also prevents loading if the start point
* exceeds the available Pokémon.
*
* @returns {void}
*/
function loadMorePokemon() {
showLoadingScreen();
startPokemon = endPokemon + 1;
endPokemon = endPokemon + 21;
if (endPokemon > 151) {
endPokemon = 151;
}
if (startPokemon >= 151) {
hideLoadBtn();
hideLoadingScreen();
return;
}
renderOverview(startPokemon, endPokemon);
}
/**
* Loads the search pool containing the first 151 Pokémon and prepends MissingNo at index 0.
*
* This ensures that the Pokémon array index matches the Pokémon ID,
* simplifying ID-based access in the search functionality.
*
* @returns {Promise<void>}
*/
async function loadSearchPool() {
let Url = "https://pokeapi.co/api/v2/pokemon?limit=151&offset=0";
let response = await fetch(Url);
let unfinishedSearchPool = await response.json();
searchPool = [missingno, ...unfinishedSearchPool.results];
}
/**
* Searches the pool of 152 Pokémon (including MissingNo) for name matches based on the user input.
*
* Prevents searching if the input has fewer than 3 characters.
* Updates the UI by enabling/disabling the search button and rendering or closing suggestions.
*/
function searchInPool() {
let inputField = getElementHelper("search-input");
let inputValue = inputField.value.trim();
emptyMatches();
if (inputValue.length >= 3) {
enableSearchBtn();
let inputRef = inputValue.toLowerCase();
pushMatchesIntoArray(inputRef);
renderSuggestions();
} else {
disableSearchBtn();
closeSuggestions();
}
}
/**
* Renders the overview for all Pokémon found by the search function.
*
* Uses the index from the `matches` array to determine the ID of each Pokémon,
* and displays their overview cards in the main container.
* Also updates UI elements like the load and back buttons.
*
* @returns {Promise<void>}
*/
async function renderOverviewMatches() {
showLoadingScreen();
let container = getElementHelper("overview-container");
container.innerHTML = "";
for (let iMatches = 0; iMatches < matches.length; iMatches++) {
renderOverviewSingleMatch(iMatches, container);
}
hideLoadingScreen();
hideLoadBtn();
showBackBtn();
}
/**
* Renders a single Pokémon card from the matches array into the given container.
*
* Retrieves the Pokémon by its index in the `matches` array, fetches its data,
* and appends the rendered card including its type icons.
*
* @param {number} iMatches - The index in the `matches` array to render.
* @param {HTMLElement} container - The HTML element where the card will be appended.
* @returns {Promise<void>}
*/
async function renderOverviewSingleMatch(iMatches, container) {
iMatchesToCurrentPokemonId = matches[iMatches].index;
currentPokemon = await getPokemonData(iMatchesToCurrentPokemonId);
const backgroundColor = typeColors[currentPokemon.types[0].type.name] || "#66aed7";
container.innerHTML += renderOverviewTemplate(currentPokemon, backgroundColor);
renderTypes(`types-container-${currentPokemon.id}`);
}
/**
* Pushes all Pokémon from the search pool whose name includes the input string into the `matches` array.
*
* Performs a case-sensitive substring match and stores both the index and the Pokémon data
* for later use in the search result rendering.
*
* @param {string} inputRef - The input string to match against Pokémon names.
*/
function pushMatchesIntoArray(inputRef) {
for (let i = 0; i < searchPool.length; i++) {
if (searchPool[i].name.includes(inputRef)) {
matches.push({ index: i, pokemon: searchPool[i] });
}
}
}
/**
* Renders search suggestions into the dropdown container.
*
* Iterates over the `matches` array and appends each Pokémon as a clickable
* list item. Each suggestion triggers a handler on click to load the selected Pokémon.
*/
function renderSuggestions() {
let container = document.getElementById("dropdown-suggestions");
container.innerHTML = "";
for (let i = 0; i < matches.length; i++) {
container.innerHTML += /*html*/ `
<p onclick="handleClickOnSuggestion(${matches[i].index})">${capitalizeFirstLetter(matches[i].pokemon.name)}</p>
`;
}
}
/**
* Handles the logic when a search suggestion is clicked.
*
* Opens the overlay for the selected Pokémon and resets the search UI.
*
* @param {number} matchId - The ID of the Pokémon selected from the search suggestions.
*/
function handleClickOnSuggestion(matchId) {
openOverlay(matchId);
emptySearchInput();
closeSuggestions();
disableSearchBtn();
}
/**
* Handles the logic when the search button is clicked.
*
* If no matches are found, it renders MissingNo as a fallback.
* Otherwise, it renders the matching Pokémon and resets the search UI.
*
* @returns {void}
*/
function handleClickOnSearchBtn() {
if (matches.length === 0) {
renderMissingNo();
return;
}
renderOverviewMatches();
emptySearchInput();
closeSuggestions();
disableSearchBtn();
}
/**
* Closes the dropdown list of search suggestions when the input field loses focus.
*
* Uses a slight delay to ensure that `onclick` events on suggestions can still register
* before the dropdown is removed.
*/
window.addEventListener("DOMContentLoaded", () => {
getElementHelper("search-input").addEventListener("blur", () => {
setTimeout(function () {
closeSuggestions();
}, 200);
});
});
/**
* Resets the view to the start of the Pokédex after a search.
*
* Clears the current content and restores the default state,
* starting from Pokémon ID 1 to 21. Also updates UI elements accordingly.
*/
function backToStart() {
let container = document.getElementById("overview-container");
showLoadingScreen();
container.innerHTML = "";
startPokemon = 1;
endPokemon = 21;
hideBackBtn();
showLoadButton();
renderOverview(startPokemon, endPokemon);
}