It Looks Like a Form.
So Why Is It a JavaScript App?
Follow the Leader looks simple — fill in some boxes, click a button. But underneath, JavaScript is doing five distinct jobs that HTML alone is completely incapable of. Here’s what’s really going on.
Every webpage is actually three languages at once
Structure. The skeleton. Headings, paragraphs, tables, buttons. HTML describes what exists on a page — but it cannot react to anything you do.
Style. The paint and furnishings. Colours, fonts, spacing, layout. CSS makes things look good — but it cannot remember anything or respond to clicks.
Behaviour. The brain. JavaScript watches what you do, reacts to it, stores data, builds new elements, and talks to the browser — all in real time.
What JavaScript is actually doing — right now
Open the HTML source of this page and search for the trail table. You will find this:
<tbody id="ftlTableBody">
<!-- Rows injected by followTheLeaderScripts.js -->
</tbody>That’s it. The table is completely empty in the HTML file. Every row — the six starter rows, and every row that appears when you type — is manufactured on the fly by JavaScript.
The script uses a function called buildRow() that constructs a brand-new table row in memory, fills it with inputs, attaches behaviours to those inputs, and then drops the finished row into the page. HTML describes fixed content. JavaScript creates content dynamically, based on what’s needed at the time.
The core browser tool for this is the DOM — the Document Object Model. Think of the DOM as a live map of every element on the page. JavaScript can read this map, add to it, change it, and delete from it at any moment.
// Create a brand-new <tr> element in memory
var tr = document.createElement('tr');
// Create a cell and an input inside it
var tdLoc = document.createElement('td');
var locIn = document.createElement('input');
locIn.type = 'text';
locIn.placeholder = 'Page name or URL…';
// Nest them: input → cell → row
tdLoc.appendChild(locIn);
tr.appendChild(tdLoc);
// Drop the finished row into the actual page
tableBody.appendChild(tr);HTML buttons do nothing on their own. A <button> tag just draws a button. JavaScript is what gives it a job. The app attaches an event listener to every interactive element — it’s like hiring a guard who watches one specific thing and springs into action the moment it changes.
Every keystroke you type in any field, every button click, even the modal opening — each of those actions triggers a specific JavaScript function that was assigned to it using addEventListener.
// "When the Save button is clicked, run saveData()"
btnSave.addEventListener('click', function () {
saveData(true);
});
// "When any input field changes, schedule an auto-save"
locIn.addEventListener('input', scheduleAutoSave);
// "When the Print button is clicked, open the print dialog"
btnPrint.addEventListener('click', function () {
window.print();
});Without event listeners, every button on the page would be decoration. With them, the page becomes genuinely interactive — it does different things depending on what you do, in real time.
This is the job that most surprises beginners. Close this tab and re-open it. Your name, period, date, quest, and all your trail stops are still there. That is not magic — it’s localStorage.
localStorage is a tiny key–value storage system built into every browser. JavaScript can write data into it and read it back out later. The data survives page refreshes and even closing and reopening the browser — until the user deliberately clears it.
The app saves everything as a single structured blob of text called JSON (JavaScript Object Notation). JSON is just a tidy way to package multiple pieces of data as one string so they can be stored together and unpacked later.
// Package all the form fields into one object
var data = {
name: getVal('ftlName'),
period: getVal('ftlPeriod'),
quest: getVal('ftlQuest'),
rows: collectRows() // array of trail stops
};
// Convert the object to a string and store it in the browser
localStorage.setItem('ftl_v1', JSON.stringify(data));
// --- Later, when the page loads again ---
var raw = localStorage.getItem('ftl_v1');
var d = JSON.parse(raw); // convert the string back to an object
setVal('ftlName', d.name); // restore each fieldThis is exactly the same concept that games use to save progress, that shopping sites use to remember your cart, and that Google Docs uses to auto-save every few seconds. The scale is different; the idea is identical.
The trail table starts with six rows. The moment you begin typing in the location field of the very last row, three new rows appear below it. You never have to click “Add” for this to happen — JavaScript is watching.
Every time you type a character in a location field, the script checks a question: “Is this the last row in the table right now?” If the answer is yes and you’ve typed at least one character, it calls growTable(3) to append three more rows immediately.
// Attached to every location input when its row is built
locIn.addEventListener('input', function () {
scheduleAutoSave();
var allRows = tableBody.querySelectorAll('tr');
// Is THIS row the last one, and does it have content?
if (tr === allRows[allRows.length - 1] && locIn.value.trim()) {
growTable(3); // append three new empty rows
}
});This is a pattern called conditional logic — the code makes a decision (if some condition is true, do something). It’s arguably the most fundamental idea in all of programming. Every app ever written — from a calculator to a self-driving car — is ultimately a large collection of “if this, then that” decisions.
Click Export Summary on the main page. A neatly formatted plain-text trail report appears instantly, ready to be copied and pasted anywhere. That text does not exist anywhere in the HTML or CSS files. JavaScript assembles it on demand, piece by piece, from whatever is currently typed into the form.
The script reads the value of every field, formats them into labelled lines, loops through every trail row collecting non-empty stops, and joins everything into a single multi-line string. The Clipboard API then lets JavaScript reach into the operating system and place that string on your clipboard — exactly as if you had manually selected the text and pressed Ctrl+C.
// Build the summary text by assembling pieces
var out = [];
out.push('TNT Follow the Leader — Trail Log');
out.push('Name: ' + document.getElementById('ftlName').value);
out.push('Quest: ' + document.getElementById('ftlQuest').value);
// ... loop through trail rows ...
var finalText = out.join('\n'); // join lines with newline
// Send the finished text to the clipboard
navigator.clipboard.writeText(finalText).then(function () {
btn.textContent = 'Copied!'; // confirm to the user
});Notice that last part — after copying, the button label changes to “Copied!” for a couple of seconds before reverting. That’s JavaScript updating the DOM (Job 1) in response to an event (Job 2), triggered by an API call. All five jobs can be in play at once.
localStorage.setItem hundreds of times per second while you type. Debouncing uses setTimeout and clearTimeout to restart a countdown on every keystroke, only running the save when you pause. It’s a tiny optimisation that every professional JavaScript developer uses constantly.
console.log(), a one-line statement that prints a message to DevTools the moment a function runs. This app has deliberate breadcrumbs in every major function — open DevTools (F12) → Console tab, reload the page, and you’ll see the startup trace: ...loadData, ...initTable: 6 rows. Type in a field and pause: ...autoSave fired (900ms idle) appears once, showing the debounce in action. That is a live execution trace of everything the program just did. See Ask Copilot Entry #039 for the full story. Build this habit now — before you have a bug to find.
Terms a beginner JS programmer uses every day
if / else statement. The engine of every decision a program makes.
You just read the source code of a real app
Every feature on this page — the auto-saving, the growing table, the export modal, the clipboard copy — is explained entirely by about 200 lines of JavaScript. No back-end server. No database. No programming language other than the one that ships in every browser on the planet, for free, by default.
That is what makes JavaScript remarkable. You could open DevTools right now (F12 on most browsers), click the Sources tab, and read every line that powers this app. Nothing is hidden. Everything you learned on this page is sitting there, legible, available to study, copy, and remix.
The five jobs you just read about — DOM manipulation, event listeners, localStorage, conditional logic, and the Clipboard API — are not beginner topics that you’ll throw away when you get serious. They are the core vocabulary of professional front-end development. Every JavaScript developer in the industry uses all five of them, every day.
Try It Yourself — Five DevTools Experiments
- Press F12 to open DevTools. Go to Application → Local Storage. Fill in your name on the trail form and watch the
ftl_v1entry update in real time as you type. - In the Console tab, type
localStorage.getItem('ftl_v1')and press Enter. You’ll see the raw JSON string that holds your entire trail. - In the Sources tab, open
followTheLeaderScripts.js. Find thebuildRowfunction. Read it — you now understand every line of it. - In the Console, type
document.getElementById('ftlName').value = 'Sherlock Holmes'and press Enter. Watch the Name field change. You just wrote JavaScript that manipulates the DOM. - In the Elements tab, find
<tbody id="ftlTableBody">. Expand it. Those<tr>elements weren’t in the original HTML — JavaScript built every one of them. - Stay in the Console tab and click Test Data. Watch the breadcrumb trace:
...loadTestData→...btnSave clicked→...saveData. Then click Print and watch...beforeprintreport how many inputs were substituted with flow divs. This is the program narrating its own execution — exactly whatconsole.log()is for.