Before this lesson

Understand HTML structure, array iteration, querySelector and textContent.

What you will be able to do

Separate repeated structure from data while preventing duplicate IDs and markup injection.

Understand the mechanism

template holds inert content that can be cloned from a DocumentFragment. The template defines structure, data supplies values, and rendering connects them. This makes write locations easier to inspect than long HTML string concatenation.

Deep cloning copies the subtree, not listeners installed with addEventListener. Bind new listeners or delegate from a parent. Store a stable record ID in data-id; dataset values are strings, so normalize comparison types.

Fixed IDs inside templates become duplicates and break labels and queries. Use local classes where possible; generate unique IDs when a label-input relationship needs them. Data attributes are small UI metadata, not private storage.

Build a fragment before inserting rather than making scattered writes, but do not assume a large performance gain without measurement. Full replacement suits a small list; complex forms require preserving focus and unsaved state.

Runnable example

Run HTML examples as separate .html files in a modern browser. Prepare resource files and multi-file modules as described in the lesson.

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Honeysuck practice</title></head><body>
<ul id="courses"></ul><template id="course-template"><li><h2 class="title"></h2><p class="summary"></p><button type="button">Mark studied</button></li></template>
<p id="status" role="status"></p><script>
const data=[{id:1,title:'HTML templates',summary:'Reuse structure safely.'},{id:2,title:'CSS layout',summary:'Arrange content.'}];
const template=document.querySelector('#course-template'),list=document.querySelector('#courses'),fragment=document.createDocumentFragment();
for(const item of data){const clone=template.content.cloneNode(true);clone.querySelector('li').dataset.id=String(item.id);clone.querySelector('.title').textContent=item.title;clone.querySelector('.summary').textContent=item.summary;fragment.append(clone);}list.replaceChildren(fragment);
list.addEventListener('click',event=>{const button=event.target.closest('button');if(!button||!list.contains(button))return;document.querySelector('#status').textContent='Studied course '+button.closest('li').dataset.id;});
</script>
</body></html>

Trace and verify

Expect two records. The second button reports ID 2 while title clicks do nothing. A title containing <img> remains literal text. Verify the template itself remains singular and clones introduce no repeated fixed IDs.

Common mistakes and boundaries

DOM position is not record identity and changes after sorting. If adding links, validate allowed URL schemes separately; textContent protects text, not href assignments.

Practice challenge

Add a checkbox and label per card. Clicking a label must activate its own input even after reverse sorting.

Reference solution and reasoning

Generate input.id and label.htmlFor from the stable course ID, not the current index. Collect all IDs and compare against a Set to check uniqueness.

Further reading