diff options
Diffstat (limited to 'js/seed/src/view.js')
-rw-r--r-- | js/seed/src/view.js | 62 |
1 files changed, 62 insertions, 0 deletions
diff --git a/js/seed/src/view.js b/js/seed/src/view.js new file mode 100644 index 0000000..5feef6e --- /dev/null +++ b/js/seed/src/view.js @@ -0,0 +1,62 @@ +// view.js +// Pure view functions + +/** + * Pure view functions for the application. + * + * Why pure functions returning HTML strings? + * - Keeps rendering logic stateless and easy to test. + * - Ensures the UI is always a direct function of state, avoiding UI bugs from incremental DOM updates. + * - Using template literals is minimal and browser-native, with no dependencies. + * + * Why escape output? + * - Prevents XSS and ensures all user/content data is safely rendered. + * + * Why semantic/accessible HTML? + * - Ensures the app is usable for all users, including those using assistive tech, and is easy to reason about. + */ +/** + * Render the app UI as an HTML string + * @param {object} state + * @returns {string} + */ +export function view(state) { + return ` + <h1>PokéDex</h1> + <container> + <form id="search-form" autocomplete="off"> + <label for="pokemon-query">Pokémon Name (or number)</label> + <input id="pokemon-query" type="text" value="${escape(state.query)}" placeholder="e.g. pikachu" aria-label="Pokémon Name" required /> + <button type="submit" ${state.loading ? 'disabled' : ''}>${state.loading ? 'Loading...' : 'Search'}</button> + </form> + ${state.error ? `<div class="error" role="alert" tabindex="-1">${escape(state.error)}</div>` : ''} + ${state.pokemon ? renderResult(state.pokemon) : ''} + </container> + `; +} + +function renderResult(pokemon) { + return ` + <div class="result"> + <h2>${capitalize(pokemon.name)} (#${pokemon.id})</h2> + <img class="pokemon-sprite" src="${pokemon.sprites.front_default}" alt="${escape(pokemon.name)} sprite" /> + <ul> + <li>Type: <b>${pokemon.types.map(t => capitalize(escape(t.type.name))).join(', ')}</b></li> + <li>Height: <b>${pokemon.height / 10} m</b></li> + <li>Weight: <b>${pokemon.weight / 10} kg</b></li> + </ul> + </div> + `; +} + +function escape(str) { + /** + * Escapes HTML special characters to prevent XSS. + * + * Why escape here? Keeps all rendering safe by default, so no accidental injection is possible. + */ + return String(str).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); +} +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} \ No newline at end of file |