Czego się nauczysz?
- Generowania dokumentów HTML dynamicznie
- Walidacji formularzy z wieloma polami
- Pracy z szablonami i stylami CSS
- Zapisywania i pobierania plików
- Tworzenia systemu historii wersji
This content is not available in your language yet.
Stworzysz Generator CV w HTML - aplikację, która na podstawie wypełnionego formularza generuje profesjonalne CV w formacie HTML. System pozwala wprowadzić dane osobowe, umiejętności, doświadczenie i edukację, a następnie wygenerować gotowy dokument.
Czego się nauczysz?
W prawdziwej pracy...
Generatory dokumentów są podstawą wielu aplikacji biznesowych - od faktur i raportów, przez certyfikaty, po CV i listy motywacyjne. Umiejętność projektowania systemów generujących dokumenty HTML z dynamicznymi danymi jest fundamentem dla każdego programisty aplikacji webowych i narzędzi biurowych.
Formularz danych CV Użytkownik wprowadza dane osobowe, kontakt, umiejętności, doświadczenie zawodowe i edukację.
Walidacja danych System sprawdza poprawność wprowadzonych danych - wymagane pola, format email, poprawność dat.
Podgląd CV Użytkownik widzi wygenerowane CV w formacie HTML przed zapisem.
Zapis/Pobranie Użytkownik może zapisać CV jako plik HTML lub przechować w historii.
Przykładowa struktura pliku JSON (historia CV):
{ "cvHistory": [ { "id": 1, "user_id": "user123", "template": "classic", "data": { "personal": { "name": "Jan Kowalski", "title": "Frontend Developer", "email": "jan.kowalski@email.pl", "phone": "+48 123 456 789", "address": "Warszawa, Polska", "linkedin": "linkedin.com/in/jankowalski", "github": "github.com/jankowalski" }, "summary": "Programista z 3-letnim doświadczeniem w tworzeniu aplikacji webowych...", "skills": ["JavaScript", "React", "CSS", "HTML", "Git"], "experience": [ { "company": "Tech Corp", "position": "Junior Frontend Developer", "from": "2024-01", "to": "2026-01", "description": "Tworzenie interfejsów użytkownika w React." } ], "education": [ { "school": "Politechnika Warszawska", "degree": "Inżynier Informatyki", "from": "2020", "to": "2024" } ], "languages": [ {"language": "Polski", "level": "ojczysty"}, {"language": "Angielski", "level": "B2"} ] }, "created_at": "2026-02-10 14:30:00" } ]}Wymagane funkcje:
Przykładowy scenariusz:
Ocena: 3.0Użytkownik wypełnia formularz: imię “Jan Kowalski”, email “jan@email.pl”, dodaje umiejętności i doświadczenie. Klika “Generuj” i widzi profesjonalne CV w HTML gotowe do wydruku.
Wszystko z wariantu A, plus:
FILTER_VALIDATE_EMAIL)htmlspecialchars() przy wyświetlaniuPrzykładowy scenariusz:
Ocena: 4.0-5.0Użytkownik generuje CV i klika “Pobierz HTML”. Plik cv-jan-kowalski.html zapisuje się na dysku. Może też zobaczyć historię swoich poprzednich CV i załadować dane do edycji.
Wszystko z wariantu B, plus:
Przykładowy scenariusz:
Ocena: 5.0-6.0Użytkownik wybiera szablon “Modern” zamiast domyślnego “Classic”. Podczas wypełniania widzi podgląd CV na żywo. Może dynamicznie dodawać kolejne pozycje doświadczenia klikając ”+”.
Walidacja danych CV:
$name = trim($_POST['name'] ?? '');$email = trim($_POST['email'] ?? '');$phone = trim($_POST['phone'] ?? '');$skills = $_POST['skills'] ?? [];
if (empty($name)) { $errors[] = "Imię i nazwisko jest wymagane";}
if (strlen($name) > 100) { $errors[] = "Imię i nazwisko może mieć maksymalnie 100 znaków";}
if (empty($email)) { $errors[] = "Email jest wymagany";}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $errors[] = "Podaj poprawny adres email";}
if (!empty($phone) && !preg_match('/^[\d\s\+\-\(\)]{9,20}$/', $phone)) { $errors[] = "Nieprawidłowy format numeru telefonu";}
if (empty($skills)) { $errors[] = "Dodaj przynajmniej jedną umiejętność";}Generowanie CV w HTML:
function generateCV(array $data, string $template = 'classic'): string { ob_start();
// Escapowanie danych $name = htmlspecialchars($data['personal']['name'] ?? '', ENT_QUOTES, 'UTF-8'); $title = htmlspecialchars($data['personal']['title'] ?? '', ENT_QUOTES, 'UTF-8'); $email = htmlspecialchars($data['personal']['email'] ?? '', ENT_QUOTES, 'UTF-8'); $phone = htmlspecialchars($data['personal']['phone'] ?? '', ENT_QUOTES, 'UTF-8'); $summary = nl2br(htmlspecialchars($data['summary'] ?? '', ENT_QUOTES, 'UTF-8'));
// Załaduj szablon include "templates/{$template}.php";
return ob_get_clean();}Szablon CV (classic.php):
<!DOCTYPE html><html lang="pl"><head> <meta charset="UTF-8"> <title>CV - <?= $name ?></title> <style> body { font-family: 'Georgia', serif; max-width: 800px; margin: 0 auto; padding: 40px; } .header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 20px; margin-bottom: 30px; } .name { font-size: 2em; margin: 0; } .title { color: #666; font-style: italic; } .contact { margin: 15px 0; color: #444; } .section { margin-bottom: 25px; } .section-title { font-size: 1.3em; border-bottom: 1px solid #ccc; padding-bottom: 5px; margin-bottom: 15px; } .skills { display: flex; flex-wrap: wrap; gap: 10px; } .skill { background: #f0f0f0; padding: 5px 15px; border-radius: 20px; } .experience-item, .education-item { margin-bottom: 15px; } .position { font-weight: bold; } .company, .school { color: #666; } .dates { color: #888; font-size: 0.9em; } </style></head><body> <div class="header"> <h1 class="name"><?= $name ?></h1> <?php if ($title): ?><p class="title"><?= $title ?></p><?php endif; ?> <div class="contact"> <?= $email ?> | <?= $phone ?> </div> </div>
<?php if ($summary): ?> <div class="section"> <h2 class="section-title">O mnie</h2> <p><?= $summary ?></p> </div> <?php endif; ?>
<div class="section"> <h2 class="section-title">Umiejętności</h2> <div class="skills"> <?php foreach ($data['skills'] ?? [] as $skill): ?> <span class="skill"><?= htmlspecialchars($skill) ?></span> <?php endforeach; ?> </div> </div>
<div class="section"> <h2 class="section-title">Doświadczenie</h2> <?php foreach ($data['experience'] ?? [] as $exp): ?> <div class="experience-item"> <div class="position"><?= htmlspecialchars($exp['position']) ?></div> <div class="company"><?= htmlspecialchars($exp['company']) ?></div> <div class="dates"><?= htmlspecialchars($exp['from']) ?> - <?= htmlspecialchars($exp['to'] ?: 'obecnie') ?></div> <p><?= nl2br(htmlspecialchars($exp['description'] ?? '')) ?></p> </div> <?php endforeach; ?> </div>
<div class="section"> <h2 class="section-title">Edukacja</h2> <?php foreach ($data['education'] ?? [] as $edu): ?> <div class="education-item"> <div class="position"><?= htmlspecialchars($edu['degree']) ?></div> <div class="school"><?= htmlspecialchars($edu['school']) ?></div> <div class="dates"><?= htmlspecialchars($edu['from']) ?> - <?= htmlspecialchars($edu['to']) ?></div> </div> <?php endforeach; ?> </div></body></html>Pobieranie pliku HTML:
function downloadCV(string $html, string $filename): void { header('Content-Type: text/html; charset=utf-8'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Content-Length: ' . strlen($html)); echo $html; exit;}
// Użycie:$html = generateCV($cvData, 'classic');$filename = 'cv-' . sanitizeFilename($cvData['personal']['name']) . '.html';downloadCV($html, $filename);
function sanitizeFilename(string $name): string { $name = strtolower($name); $name = preg_replace('/[^a-z0-9]+/', '-', $name); return trim($name, '-');}Zapisywanie do historii:
function saveCVToHistory(array &$history, array $cvData, string $template): int { $newId = empty($history) ? 1 : max(array_column($history, 'id')) + 1;
$entry = [ 'id' => $newId, 'user_id' => $_SESSION['user_id'] ?? 'guest', 'template' => $template, 'data' => $cvData, 'created_at' => date('Y-m-d H:i:s'), ];
$history[] = $entry; return $newId;}
function getCVHistory(array $history, string $userId): array { return array_filter($history, fn($cv) => $cv['user_id'] === $userId);}Dostępne szablony:
function getTemplates(): array { return [ 'classic' => [ 'name' => 'Klasyczny', 'description' => 'Elegancki, tradycyjny styl', 'css' => 'cv-classic.css', ], 'modern' => [ 'name' => 'Nowoczesny', 'description' => 'Minimalistyczny, świeży design', 'css' => 'cv-modern.css', ], 'minimal' => [ 'name' => 'Minimalny', 'description' => 'Prosty, czytelny układ', 'css' => 'cv-minimal.css', ], ];}JavaScript - dynamiczne dodawanie sekcji:
function addExperienceEntry() { const container = document.getElementById('experience-container'); const index = container.querySelectorAll('.experience-entry').length;
const html = ` <div class="experience-entry" data-index="${index}"> <input type="text" name="experience[${index}][company]" placeholder="Firma" required> <input type="text" name="experience[${index}][position]" placeholder="Stanowisko" required> <input type="month" name="experience[${index}][from]" required> <input type="month" name="experience[${index}][to]"> <textarea name="experience[${index}][description]" placeholder="Opis obowiązków"></textarea> <button type="button" onclick="removeEntry(this)">Usuń</button> </div> `;
container.insertAdjacentHTML('beforeend', html);}
function removeEntry(button) { button.closest('.experience-entry, .education-entry').remove();}Wykorzystaj lekcje!
Cotygodniowe spotkania podczas lekcji to idealny moment, by:
Pracuj iteracyjnie - lepiej mieć działający wariant A niż niedokończony C!