Day 12: Create Elements & Render Lists from Data
✅ Today you will learn
- createElement, append, prepend, before, after, remove
- Rendering a product grid from an array of objects
- Template literals vs createElement vs <template>
- DocumentFragment for fast batch inserts
- State → render() → UI: the pattern React is built on
🏢 In the real world
The menu changes every day: new sweets, sold-out items, price updates. Editing HTML by hand each morning is impossible. Real websites keep products as data (from a database or API) and generate the HTML with JavaScript. Today you build the Sharma Sweets product grid from an array – the same technique tomorrow’s API data will use.
Creating and inserting elements
<ul id="list"><li>Kaju Katli</li></ul>
<button id="add">append</button> <button id="first">prepend</button> <button id="del">remove last</button>
<script>
const list = document.querySelector('#list');
document.querySelector('#add').addEventListener('click', () => {
const li = document.createElement('li'); // 1. create
li.textContent = 'Ghewar ' + (list.children.length + 1); // 2. fill (safe text)
li.className = 'new';
list.append(li); // 3. insert at the end
});
document.querySelector('#first').addEventListener('click', () => {
const li = document.createElement('li');
li.textContent = '⭐ Today’s special';
list.prepend(li); // insert at the start
});
document.querySelector('#del').addEventListener('click', () => {
list.lastElementChild?.remove(); // remove (?. = only if it exists)
});
</script>| Method | Puts the node… |
|---|---|
| parent.append(node, "text") | At the end inside parent (several at once) |
| parent.prepend(node) | At the start inside parent |
| el.before(node) / el.after(node) | Just outside el, before or after it |
| el.replaceWith(node) | Instead of el |
| el.remove() | Deletes el |
| parent.replaceChildren(...nodes) | Clears parent and inserts the new nodes – perfect for re-rendering |
Render a product grid from data
<style>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #fff; }
.card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.card div { padding: 8px 10px; }
.card button { width: 100%; margin-top: 6px; }
.card.out { opacity: .55; }
</style>
<div class="grid" id="grid"></div>
<script>
const products = [
{ id: 1, name: 'Kaju Katli', price: 900, image: 'images/kaju-katli-400.webp', stock: 25 },
{ id: 2, name: 'Malai Ghewar', price: 650, image: 'images/ghewar-400.webp', stock: 8 },
{ id: 3, name: 'Rasmalai', price: 700, image: 'images/rasmalai-400.webp', stock: 0 },
{ id: 4, name: 'Besan Ladoo', price: 500, image: 'images/ladoo-400.webp', stock: 40 },
];
function createCard(p) {
const card = document.createElement('article');
card.className = 'card' + (p.stock === 0 ? ' out' : '');
const img = document.createElement('img');
img.src = p.image;
img.alt = p.name;
img.loading = 'lazy';
const body = document.createElement('div');
const title = document.createElement('b');
title.textContent = p.name; // text is always safe
const price = document.createElement('div');
price.textContent = `₹${p.price}/kg`;
const btn = document.createElement('button');
btn.textContent = p.stock === 0 ? 'Sold out' : 'Add to cart';
btn.disabled = p.stock === 0;
btn.dataset.id = p.id;
body.append(title, price, btn);
card.append(img, body);
return card;
}
document.querySelector('#grid').replaceChildren(...products.map(createCard));
</script>Three ways to build HTML from data
| Technique | Pros | Cons |
|---|---|---|
| Template literal + innerHTML | Short and readable | XSS risk with user data; replaces listeners inside |
| createElement ⭐ | Safe (textContent), precise | More lines |
| <template> + cloneNode | HTML stays in HTML, safe filling | Needs a template in the page |
| Frameworks (React, Vue) | Automatic efficient updates | Learn after vanilla JS |
<template id="row">
<tr><td class="name"></td><td class="qty"></td><td class="amount"></td></tr>
</template>
<table border="1" cellpadding="6" style="border-collapse:collapse">
<thead><tr><th>Item</th><th>Qty</th><th>Amount</th></tr></thead>
<tbody id="body"></tbody>
</table>
<script>
const lines = [
{ name: 'Kaju Katli', qty: 2, price: 900 },
{ name: 'Rasmalai', qty: 1, price: 700 },
];
const tpl = document.querySelector('#row');
const fragment = document.createDocumentFragment(); // build off-screen
for (const line of lines) {
const row = tpl.content.cloneNode(true);
row.querySelector('.name').textContent = line.name;
row.querySelector('.qty').textContent = line.qty + ' kg';
row.querySelector('.amount').textContent = '₹' + line.qty * line.price;
fragment.append(row);
}
document.querySelector('#body').append(fragment); // ONE insert into the page
</script>State → render() → UI
<style>
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.card { border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #fff; }
.card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.card div { padding: 8px 10px; }
.card button { width: 100%; margin-top: 6px; }
.card.out { opacity: .55; }
</style>
<div class="grid" id="grid"></div>
<p id="cart"><b>Cart:</b> empty</p>
<script>
const products = [
{ id: 1, name: 'Kaju Katli', price: 900, image: 'images/kaju-katli-400.webp', stock: 25 },
{ id: 2, name: 'Malai Ghewar', price: 650, image: 'images/ghewar-400.webp', stock: 8 },
{ id: 3, name: 'Rasmalai', price: 700, image: 'images/rasmalai-400.webp', stock: 0 },
{ id: 4, name: 'Besan Ladoo', price: 500, image: 'images/ladoo-400.webp', stock: 40 },
];
const state = { cart: {} }; // id → qty
function render() {
const grid = document.querySelector('#grid');
grid.innerHTML = '';
for (const p of products) {
const qty = state.cart[p.id] || 0;
const card = document.createElement('article');
card.className = 'card' + (p.stock === 0 ? ' out' : '');
card.innerHTML = `<img src="${p.image}" alt=""><div><b></b><div>₹${p.price}/kg</div>
<button data-id="${p.id}" ${p.stock === 0 ? 'disabled' : ''}>${qty ? 'Add more (' + qty + ')' : 'Add to cart'}</button></div>`;
card.querySelector('b').textContent = p.name;
grid.append(card);
}
const lines = products.filter(p => state.cart[p.id]).map(p => `${p.name} × ${state.cart[p.id]}`);
const total = products.reduce((s, p) => s + (state.cart[p.id] || 0) * p.price, 0);
document.querySelector('#cart').innerHTML = '<b>Cart:</b> ' + (lines.length ? lines.join(', ') + ` = ₹${total}` : 'empty');
}
document.querySelector('#grid').addEventListener('click', e => { // delegation (Day 11)
const btn = e.target.closest('button[data-id]');
if (!btn) return;
const id = Number(btn.dataset.id);
state.cart[id] = (state.cart[id] || 0) + 1; // 1. update state
render(); // 2. re-render
});
render();
</script>In React you write setCart(...) and React calls your component (render) for you, updating only what changed. Understanding this vanilla version makes React feel natural.
🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
How do you efficiently add 1,000 items to the DOM?
Build them in a DocumentFragment (or build one HTML string with escaped data) and insert once, avoid reading layout properties inside the loop, and consider virtualisation (render only visible rows) for very long lists.
Why do frameworks use a render function instead of manual DOM edits?
Deriving the UI from state in one place keeps it consistent and predictable; the framework diffs the result and applies minimal DOM changes, avoiding bugs where some parts of the page forget to update.
Summary
- createElement → fill with textContent → append/prepend; remove() to delete.
- Render lists from arrays with map/forEach and replaceChildren.
- <template> + DocumentFragment for clean, fast batches.
- State → render() → UI; events update state, never the DOM directly.
Day 13: forms – read values with FormData, validate like a professional and show helpful error messages.
