Type-Specific Fields

Personal Catalogue: PHP & MySQL

Chapter 4 · Type-Specific Fields

Chapter 3's add/edit forms already work, but they show one generic "Creator" label and one generic "Format detail" label no matter which item type is selected — technically correct, since those are the real column names, but not very honest to the person actually filling the form in. A CD's "creator" is really its artist; a DVD's is really its director. This chapter makes the same single form feel type-aware without touching the database schema at all.

Recap: One Shared Schema, Several Real Meanings
Chapter 2 already established this table — it's worth having it in view again before building the UI on top of it:
ColumnBookCDDVD / Blu-ray
creatorAuthorArtistDirector
format_detailHardcover / paperbackSingle / albumRegion code

The Core Idea: Data-Driven Labels, Not a Fork in the Form

A tempting but wrong approach here is to build a genuinely separate form for each item type — a "book form," a "CD form," a "DVD form" — each with its own field names. That would mean four times the markup to maintain, and four separate branches of PHP to validate and insert. Since the underlying items table is deliberately shared (Chapter 2), the form should stay shared too: one set of inputs, with their labels — not their names, not their database columns — changing based on whatever item_type_id is currently selected.

That's a job for a small amount of vanilla JavaScript running in the browser, driven by a plain lookup object built once from the same item_types data the form already renders its dropdown from.

Building the Label Map

Rather than hard-coding the label text in JavaScript (where it could drift out of sync with the item types actually stored in the database), the label map is generated server-side, from PHP, as a small inline <script> block that runs once when the page loads:

add_item.php (inside the <body>, after the form)
<script> // Built from a plain PHP array, not hand-typed twice, so it can never // silently disagree with what's actually in item_types. const creatorLabels = <?= json_encode([ 'book' => 'Author', 'cd' => 'Artist', 'dvd' => 'Director', 'bluray' => 'Director', ]) ?>; const formatLabels = <?= json_encode([ 'book' => 'Binding (hardcover / paperback)', 'cd' => 'Release type (single / album)', 'dvd' => 'Region code', 'bluray' => 'Region code', ]) ?>; </script>

json_encode() turns a plain PHP associative array into a real JavaScript object literal — this is the same real technique PHP Intermediate Chapter 7 covers for turning PHP data into JSON generally, just used here to hand a small config object to a page's own inline script rather than to an API response.

Wiring the Dropdown to the Labels

The item-type <select> already exists from Chapter 3. Two small changes make it drive the labels: giving the <select> and the two label elements real id attributes, and each <option> a data-name attribute holding the real item_types.name value ('book', 'cd', and so on) — not the numeric id, since that's what the label maps above are keyed by:

<select name="item_type_id" id="item-type-select" required> <option value="">-- choose --</option> <?php foreach ($itemTypes as $type): ?> <option value="<?= $type['id'] ?>" data-name="<?= htmlspecialchars($type['name']) ?>"> <?= htmlspecialchars($type['name']) ?> </option> <?php endforeach; ?> </select> ... <label id="creator-label" for="creator-input">Creator:</label> <input type="text" name="creator" id="creator-input"> <label id="format-label" for="format-input">Format detail:</label> <input type="text" name="format_detail" id="format-input">

And the script that actually reacts to a change:

const select = document.getElementById('item-type-select'); const creatorLabel = document.getElementById('creator-label'); const formatLabel = document.getElementById('format-label'); function updateLabels() { const selectedOption = select.options[select.selectedIndex]; const typeName = selectedOption.dataset.name; creatorLabel.textContent = (creatorLabels[typeName] ?? 'Creator') + ':'; formatLabel.textContent = (formatLabels[typeName] ?? 'Format detail') + ':'; } select.addEventListener('change', updateLabels); // Run once on load too, in case a type is already selected // (e.g. the edit form, where an item's own type is pre-chosen). updateLabels();
Why the Fallback ('Creator', not undefined)
creatorLabels[typeName] ?? 'Creator' matters for one real edge case: the blank -- choose -- option has no data-name at all, so typeName is undefined, and a lookup on undefined correctly misses every key in the object. Without the ?? fallback, the label would read the literal text "undefined:" until a real type is picked — a small but genuinely confusing bug if it isn't guarded against.

This Is Cosmetic, Not Validation

Relabeling a text input doesn't change what can actually be typed into it — nothing stops someone from selecting "book" and then typing "Region 2" into what's now labeled "Binding" anyway. This chapter's own JavaScript is a genuine usability improvement, not a data-integrity mechanism, and it must never be treated as one.

Client-Side Relabeling Is Not Server-Side Validation
JavaScript can be disabled, blocked, or simply never run (a script tag failing to load, a browser extension interfering) — and even when it runs perfectly, a request can always be sent directly to add_item.php with a tool like curl, bypassing the form (and its labels) entirely. Every real validation rule — "Title is required," "please choose an item type" — must stay enforced in the PHP that actually runs the INSERT/UPDATE, exactly as Chapter 3 already does it, regardless of what the browser's own UI happens to show.

A Small, Genuinely Type-Specific Validation Rule

One place type does matter server-side: release_year. A book or CD's real release year is reasonably bounded — nothing published before, say, 1400 belongs in this catalogue — but the exact sensible range differs slightly by type in a way that's not worth over-engineering. A single, deliberately loose sanity check catches genuine typos (a four-digit year with a stray extra digit, or an obviously impossible future date) without needing a separate rule per item type:

if ($releaseYear !== '') { $year = (int) $releaseYear; $currentYear = (int) date('Y'); if ($year < 1400 || $year > $currentYear + 1) { $errors[] = "Release year looks wrong: {$releaseYear}."; } }

The + 1 allows for an item releasing next calendar year (a pre-order), while still catching an obvious typo like 29026.

Hands-On Exercises

Exercise 1

Add the data-name attributes, the two label elements, and the JavaScript from this chapter to your own add_item.php. Confirm switching the dropdown between "book," "cd," and "dvd" correctly updates both labels without a page reload.

📄 View solution
Exercise 2

Add the same label-switching script to edit_item.php, and confirm that opening the edit form for an existing DVD shows "Director" and "Region code" correctly pre-labeled on page load, before any dropdown change happens.

📄 View solution
Exercise 3

Add the release_year sanity check from this chapter to add_item.php's own validation, then confirm submitting a year of 29026 is correctly rejected with an error message, while 2027 (next year) is correctly accepted.

📄 View solution

Chapter 4 Quick Reference

  • One shared form — labels change per type via JavaScript; database columns and field names stay the same
  • Label maps — built server-side with json_encode() so they can never drift from the real item_types data
  • data-name — each option carries the real type name, since the label maps are keyed by name, not by numeric id
  • Cosmetic vs. validation — relabeling never replaces server-side checks; every real rule still lives in the PHP that runs the query
  • release_year sanity check — one loose, shared range rule rather than four separate per-type rules
  • Next chapter: Tags — attaching, managing, and filtering by tag on books