Dziennik krok 7: ten sam model na stronie
Projekt: dziennik ocen krok po kroku - JavaScript od podstaw: konsola (HTML/CSS/JS)
Ostatni krok: klasa `Gradebook` przechodzi na stronę bez żadnej zmiany. Widokiem jest teraz tabela HTML budowana przez `createElement`, a ocenę dopisuje formularz. Pola formularza są sklejane w wiersz `uczeń;przedmiot;ocena` i sprawdzane tą samą metodą `parseLine`, więc błędy wyglądają tak samo jak w konsoli. Spróbuj dodać ocenę 7 albo zostawić puste imię.
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>Dziennik</title>
<style>
body { font-family: sans-serif; max-width: 36em; }
table { border-collapse: collapse; margin: 0.5em 0; }
th, td { border: 1px solid #94a3b8; padding: 0.2em 0.6em; }
input, button { font: inherit; padding: 0.2em 0.4em; width: 8em; }
button { width: auto; }
#msg { min-height: 1.4em; }
</style>
</head>
<body>
<h1>Dziennik</h1>
<p>
<input id="student" placeholder="Uczeń">
<input id="subject" placeholder="Przedmiot">
<input id="grade" placeholder="Ocena">
<button id="add">Dodaj</button>
</p>
<p id="msg"></p>
<table id="report">
<thead><tr><th>Uczeń</th><th>Oceny</th><th>Średnia</th></tr></thead>
<tbody></tbody>
</table>
<script>
const DATA = [
'Ola;matematyka;5', 'Jan;matematyka;3', 'Ola;polski;4', 'Ewa;matematyka;6',
'Jan;polski;2', 'Ewa;polski;5', 'Ola;matematyka;6', 'Jan;matematyka;4',
].join('\n');
class Gradebook {
#records = [];
static parseLine(line) {
const parts = line.split(';');
if (parts.length !== 3) throw new Error('oczekiwano 3 pól');
const [name, subject, gradeText] = parts.map(p => p.trim());
if (name === '' || subject === '') throw new Error('puste imię lub przedmiot');
const grade = Number(gradeText);
if (!Number.isInteger(grade) || grade < 1 || grade > 6) {
throw new Error(`ocena spoza skali 1-6: "${gradeText}"`);
}
return { name, subject, grade };
}
static average(grades) {
return grades.reduce((a, b) => a + b, 0) / grades.length;
}
add(record) {
this.#records.push(record);
}
load(text) {
const errors = [];
text.split('\n').forEach((line, i) => {
try {
this.add(Gradebook.parseLine(line));
} catch (e) {
errors.push(`wiersz ${i + 1}: ${e.message}`);
}
});
return errors;
}
gradesBy(key) {
const groups = {};
for (const r of this.#records) {
if (!(r[key] in groups)) groups[r[key]] = [];
groups[r[key]].push(r.grade);
}
return groups;
}
best() {
let best = null;
for (const [name, grades] of Object.entries(this.gradesBy('name'))) {
const avg = Gradebook.average(grades);
if (best === null || avg > best.avg) best = { name, avg };
}
return best;
}
get records() {
return [...this.#records];
}
}
const book = new Gradebook();
book.load(DATA);
const msg = document.getElementById('msg');
function cells(tr, texts) {
for (const text of texts) {
const td = document.createElement('td');
td.textContent = text;
tr.appendChild(td);
}
}
function render() {
const tbody = document.querySelector('#report tbody');
tbody.replaceChildren();
for (const [name, grades] of Object.entries(book.gradesBy('name'))) {
const tr = document.createElement('tr');
cells(tr, [name, grades.join(' '), Gradebook.average(grades).toFixed(2)]);
tbody.appendChild(tr);
}
}
document.getElementById('add').addEventListener('click', () => {
const line = ['student', 'subject', 'grade'].map(id => document.getElementById(id).value).join(';');
try {
book.add(Gradebook.parseLine(line));
msg.textContent = 'Dodano ocenę';
render();
} catch (e) {
msg.textContent = 'Błąd: ' + e.message;
}
});
render();
</script>
</body>
</html>Ładowanie interaktywnego edytora... Jeśli się nie pojawi, włącz JavaScript.
Wszystkie lekcje kursu JavaScript od podstaw: konsola·Zaloguj się, aby zapisać postęp na koncie.