about summary refs log tree commit diff stats
path: root/js/seed/src/view.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/seed/src/view.js')
-rw-r--r--js/seed/src/view.js62
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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
+}
+function capitalize(str) {
+  return str.charAt(0).toUpperCase() + str.slice(1);
+} 
\ No newline at end of file