HTML loop in JavaScript
August 26, 2025 · AI Conversations
tickets
:
Array(2)
0
:
{section: 'tiered', type: 'ADULT', price: 18, quantity: 2}
1
:
{section: 'tiered', type: 'CHILD', price: 14, quantity: 1}
now I have a event loop
each event has section,type and price
Now I want to fill quantity if its avaul tickets array otherwise set it 0 based match section,type and price
Here’s a clean way to do it:
const tickets = [
{ section: 'tiered', type: 'ADULT', price: 18, quantity: 2 },
{ section: 'tiered', type: 'CHILD', price: 14, quantity: 1 }
];
const events = [
{ section: 'tiered', type: 'ADULT', price: 18 },
{ section: 'tiered', type: 'CHILD', price: 14 },
{ section: 'tiered', type: 'SENIOR', price: 12 },
{ section: 'vip', type: 'ADULT', price: 30 }
];
// Map tickets for quick lookup
const ticketMap = new Map(
tickets.map(t => [`${t.section}_${t.type}_${t.price}`, t.quantity])
);
const merged = events.map(ev => {
const key = `${ev.section}_${ev.type}_${ev.price}`;
return {
...ev,
quantity: ticketMap.get(key) || 0
};
});
console.log(merged);
✅ Output:
[
{ section: 'tiered', type: 'ADULT', price: 18, quantity: 2 },
{ section: 'tiered', type: 'CHILD', price: 14, quantity: 1 },
{ section: 'tiered', type: 'SENIOR', price: 12, quantity: 0 },
{ section: 'vip', type: 'ADULT', price: 30, quantity: 0 }
]
This way, if a match exists in `tickets`, quantity comes from it; otherwise, it defaults to `0`.
👉 Do you also want to handle **cases where section/type matches but price differs slightly** (like float rounding), or should it be an **exact match** only?
### 1) Fast (precompute a lookup, then use in your loop)
const tickets = [
{ section: 'tiered', type: 'ADULT', price: 18, quantity: 2 },
{ section: 'tiered', type: 'CHILD', price: 14, quantity: 1 }
];
const events = [
{ section: 'tiered', type: 'ADULT', price: 18 },
{ section: 'tiered', type: 'CHILD', price: 14 },
{ section: 'tiered', type: 'SENIOR', price: 12 },
{ section: 'vip', type: 'ADULT', price: 30 }
];
// Build a quick lookup once
const keyOf = (obj) => `${obj.section}|${obj.type}|${Number(obj.price)}`;
const ticketMap = new Map(tickets.map(t => [keyOf(t), t.quantity]));
// Build HTML
let html = '';
for (const ev of events) {
const qty = ticketMap.get(keyOf(ev)) ?? 0;
html += `
<div class="ticket-row" data-section="${ev.section}" data-type="${ev.type}" data-price="${ev.price}">
<span class="sec">${ev.section}</span>
<span class="type">${ev.type}</span>
<span class="price">${ev.price}</span>
<input type="number" class="qty" min="0" value="${qty}">
</div>
`;
}
document.getElementById('tickets-list').innerHTML = html;
### 2) Simple (find inside the loop)
let html = '';
for (const ev of events) {
const found = tickets.find(t =>
t.section === ev.section &&
t.type === ev.type &&
Number(t.price) === Number(ev.price)
);
const qty = found?.quantity ?? 0;
html += `
<div class="ticket-row">
<span>${ev.section}</span>
<span>${ev.type}</span>
<span>${ev.price}</span>
<input type="number" class="qty" min="0" value="${qty}">
</div>
`;
}
document.getElementById('tickets-list').innerHTML = html;
> Tip: If prices can be floats/strings, normalize with `Number()` (or `toFixed(2)`) in both places so keys match reliably.
// Update order info block
const orderInfoBlock = document.querySelector('.order-info-block');
if (orderInfoBlock) {
orderInfoBlock.innerHTML = `
Order Information
${eventData.event_date_string}
${eventData.venue_title}
${eventData.venue_full}
`;
}
const html = eventData.tickets.map(section => {
const sectionId = section.title.toLowerCase().replace(/s+/g, '_');
return `
${section.title}
${section.seating_type}
Ticket Type
Price Per Person
Number of Tickets*
${section.ticket_types.map(type => `
${type.label}
Online Special
€${type.price}
–
+
`).join('')}
`}).join('');
Drop these helpers **before** your `const html = …`:
// Build a quick lookup from existing orderData.tickets
const keyOf = (sectionId, label, price) => `${String(sectionId).toLowerCase()}|${String(label)}|${Number(price)}`;
const qtyMap = new Map(
(orderData.tickets || []).map(t => [keyOf(t.section, t.type, t.price), Number(t.quantity) || 0])
);
const getQty = (sectionId, label, price) => qtyMap.get(keyOf(sectionId, label, price)) ?? 0;
Then use `getQty(…)` when rendering each ticket input:
const html = eventData.tickets.map(section => {
const sectionId = section.title.toLowerCase().replace(/s+/g, '_');
return `
<div class="seating-section">
<span class="seating-title ${section.css_class}">
<h2>${section.title}</h2>
<p class="seating-type">${section.seating_type}</p>
</span>
<div class="ticket-grid">
${section.ticket_types.map(type => `
<div class="ticket-type">
<div class="ticket-type-title">${type.label}</div>
<div class="price-deets">
<div class="price-label">Online Special</div>
<div class="price" style="width: 30%;">€${type.price}</div>
</div>
<div class="ticket-quantity">
<div class="quantity-selector">
<button type="button" class="quantity-btn minus ticket-quantity-btn"
data-section="${sectionId}" data-ticket-type="${type.label}" data-price="${type.price}">-</button>
<input type="number" class="quantity-value"
data-section="${sectionId}" data-ticket-type="${type.label}" data-price="${type.price}"
value="${getQty(sectionId, type.label, type.price)}"
min="0" max="${type.seats_left || 10}" readonly>
<button type="button" class="quantity-btn plus ticket-quantity-btn"
data-section="${sectionId}" data-ticket-type="${type.label}" data-price="${type.price}">+</button>
</div>
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
This matches by `sectionId` (derived from the section title), `type.label`, and `price`. If no match is found in `orderData.tickets`, the input defaults to `0`.
Here’s the full updated version with the `orderData.tickets` check:
// Helper to build a key for matching
const keyOf = (sectionId, label, price) =>
`${String(sectionId).toLowerCase()}|${String(label)}|${Number(price)}`;
// Build qtyMap only if tickets exist
let qtyMap = new Map();
if (orderData && Array.isArray(orderData.tickets) && orderData.tickets.length > 0) {
qtyMap = new Map(
orderData.tickets.map(t => [
keyOf(t.section, t.type, t.price),
Number(t.quantity) || 0
])
);
}
// Getter to use inside your loop
const getQty = (sectionId, label, price) =>
qtyMap.get(keyOf(sectionId, label, price)) ?? 0;
Then in your HTML loop, just use:
<input type="number" class="quantity-value"
data-section="${sectionId}"
data-ticket-type="${type.label}"
data-price="${type.price}"
value="${getQty(sectionId, type.label, type.price)}"
min="0" max="${type.seats_left || 10}" readonly>
✅ If `orderData.tickets` is missing or empty → all values default to `0`.
✅ If `orderData.tickets` is present → quantities will prefill correctly.
Do you want me to also make it so **if `orderData.tickets` is not defined at all** (like `undefined`), it skips building the map entirely?
– `sectionId` you render (from `section.title`) ≠ `orderData.tickets[i].section` (e.g. `'tiered_seating'` vs `'tiered'`)
– `type.label` casing/spacing differs from `tickets[i].type`
– `price` is a string vs number (or has `€`)
Use a **stable normalizer** on both sides and a tolerant lookup:
// ---- Normalizers ----
const normalizeSection = (s) =>
String(s || '')
.toLowerCase()
.replace(/[^w]+/g, '_') // spaces & punctuation -> _
.replace(/^_+|_+$/g, ''); // trim underscores
const normalizeType = (s) => String(s || '').trim().toUpperCase();
const normalizePrice = (p) => {
// strip symbols & commas, convert to number
const n = Number(String(p).replace(/[^d.-]/g, ''));
return Number.isFinite(n) ? n : 0;
};
// composite key
const keyOf = (sectionKey, type, price) =>
`${normalizeSection(sectionKey)}|${normalizeType(type)}|${normalizePrice(price)}`;
// ---- Build qty map from orderData (safe) ----
let qtyMap = new Map();
if (orderData && Array.isArray(orderData.tickets) && orderData.tickets.length) {
// build multiple candidate keys per ticket to survive different "section" sources
const entries = [];
for (const t of orderData.tickets) {
const sec = t.section; // e.g. "tiered"
const typ = t.type; // e.g. "ADULT"
const pri = t.price; // e.g. 18
// primary key (as stored)
entries.push([keyOf(sec, typ, pri), Number(t.quantity) || 0]);
// also add a forgiving variant with underscores (if someone used title in UI)
entries.push([keyOf(normalizeSection(sec), typ, pri), Number(t.quantity) || 0]);
}
qtyMap = new Map(entries);
}
// ---- Getter with tolerant fallback ----
const getQty = (sectionKey, typeLabel, price) => {
// 1) fast path via map
const k = keyOf(sectionKey, typeLabel, price);
if (qtyMap.has(k)) return qtyMap.get(k);
// 2) tolerant search if map miss (handles slight price/label/section differences)
if (orderData && Array.isArray(orderData.tickets)) {
const targetSec = normalizeSection(sectionKey);
const targetType = normalizeType(typeLabel);
const targetPrice = normalizePrice(price);
const found = orderData.tickets.find(t =>
normalizeSection(t.section) === targetSec &&
normalizeType(t.type) === targetType &&
Math.abs(normalizePrice(t.price) - targetPrice) < 0.001 // float-safe
);
if (found) return Number(found.quantity) || 0;
}
return 0;
};
Now, when building your HTML, **use a section key that actually matches your stored data**. If your stored `tickets[i].section` is like `'tiered'`, don’t derive it from the title—prefer a stable field like `section.css_class` or `section.slug`. Example:
const html = eventData.tickets.map(section => {
// Prefer a stable key; fall back to title if needed
const sectionKey = section.css_class || section.slug || section.title;
return `
<div class="seating-section">
<span class="seating-title ${section.css_class}">
<h2>${section.title}</h2>
<p class="seating-type">${section.seating_type}</p>
</span>
<div class="ticket-grid">
${section.ticket_types.map(type => `
<div class="ticket-type">
<div class="ticket-type-title">${type.label}</div>
<div class="price-deets">
<div class="price-label">Online Special</div>
<div class="price" style="width: 30%;">€${type.price}</div>
</div>
<div class="ticket-quantity">
<div class="quantity-selector">
<button type="button" class="quantity-btn minus ticket-quantity-btn"
data-section="${normalizeSection(sectionKey)}"
data-ticket-type="${type.label}"
data-price="${type.price}">-</button>
<input type="number" class="quantity-value"
data-section="${normalizeSection(sectionKey)}"
data-ticket-type="${type.label}"
data-price="${type.price}"
value="${getQty(sectionKey, type.label, type.price)}"
min="0" max="${type.seats_left || 10}" readonly>
<button type="button" class="quantity-btn plus ticket-quantity-btn"
data-section="${normalizeSection(sectionKey)}"
data-ticket-type="${type.label}"
data-price="${type.price}">+</button>
</div>
</div>
</div>
`).join('')}
</div>
</div>
`;
}).join('');
### Quick debug (optional)
Add this once to see why it mismatches:
console.log('qtyMap keys:', [...qtyMap.keys()]);
// inside the map for each `type`:
// console.log('lookup key:', keyOf(sectionKey, type.label, type.price));
If you see `qtyMap` has `tiered|ADULT|18` but your lookup is `tiered_seating|ADULT|18`, switch your `sectionKey` source to match what’s in `orderData.tickets` (e.g., use `section.css_class` = `'tiered'`).
Here’s a clean version tailored for your case:
// ---- Normalizers ----
const normalizeSection = (s) =>
String(s || '').trim().toLowerCase().replace(/[^w]+/g, '_');
const normalizeType = (s) =>
String(s || '').trim().toLowerCase();
const normalizePrice = (p) =>
Number(String(p).replace(/[^d.-]/g, ''));
// composite key
const keyOf = (sectionKey, type, price) =>
`${normalizeSection(sectionKey)}|${normalizeType(type)}|${normalizePrice(price)}`;
// ---- Build qty map from orderData (safe) ----
let qtyMap = new Map();
if (orderData && Array.isArray(orderData.tickets) && orderData.tickets.length) {
qtyMap = new Map(
orderData.tickets.map(t => [
keyOf(t.section, t.type, t.price),
Number(t.quantity) || 0
])
);
}
// ---- Getter ----
const getQty = (sectionKey, typeLabel, price) =>
qtyMap.get(keyOf(sectionKey, typeLabel, price)) ?? 0;
### Usage in your HTML loop
<input type="number" class="quantity-value"
data-section="${sectionId}"
data-ticket-type="${type.label}"
data-price="${type.price}"
value="${getQty(sectionId, type.label, type.price)}"
min="0" max="${type.seats_left || 10}" readonly>
—
✅ Now `tiered`, `TIERED`, `Tiered` all match.
✅ `adult`, `ADULT`, `Adult` also match.
✅ Price is still an exact number match.
Do you also want me to make **price tolerant** (e.g. `"18.00"` vs `18` vs `"€18"`), or should it stay exact number equality?