4. Этап 3 — Интерактивность 5 баллов
Цель этапа
Сделать приложение интерактивным: пользователь может добавлять и удалять процессы, сортировать таблицу и фильтровать по зонам.
3.1 Форма добавления процесса
Замените содержимое секции #add-form в index.html:
<section id="add-form">
<h2>Добавить процесс</h2>
<form id="process-form">
<div class="form-row">
<label>Название:
<input type="text" id="inp-name" required placeholder="Название процесса">
</label>
<label>Тип:
<select id="inp-type">
<option value="Основной">Основной</option>
<option value="Поддерживающий">Поддерживающий</option>
<option value="Управленческий">Управленческий</option>
</select>
</label>
</div>
<fieldset>
<legend>Операционные критерии (1–5)</legend>
<div class="form-row">
<label>Время цикла: <input type="number" id="inp-cycleTime" min="1" max="5" value="3"></label>
<label>Стоимость: <input type="number" id="inp-cost" min="1" max="5" value="3"></label>
<label>Автоматизация: <input type="number" id="inp-automation" min="1" max="5" value="3"></label>
<label>Доля переделок: <input type="number" id="inp-rework" min="1" max="5" value="3"></label>
</div>
</fieldset>
<fieldset>
<legend>Стратегические критерии (1–5)</legend>
<div class="form-row">
<label>Стратег. значимость: <input type="number" id="inp-strategicValue" min="1" max="5" value="3"></label>
<label>Критичность: <input type="number" id="inp-criticality" min="1" max="5" value="3"></label>
<label>Инновац. потенциал: <input type="number" id="inp-innovation" min="1" max="5" value="3"></label>
<label>Техдолг: <input type="number" id="inp-techDebt" min="1" max="5" value="3"></label>
</div>
</fieldset>
<button type="submit" class="btn-add">Добавить</button>
</form>
</section>
Добавьте стили для формы в style.css:
/* --- Форма --- */
.form-row {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 12px;
}
.form-row label {
display: flex;
flex-direction: column;
font-size: 0.85rem;
color: #555;
}
.form-row input,
.form-row select {
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 0.9rem;
margin-top: 4px;
}
fieldset {
border: 1px solid #e0e0e0;
border-radius: 6px;
padding: 12px 16px;
margin-bottom: 12px;
}
legend {
font-weight: 600;
color: #1a237e;
font-size: 0.9rem;
}
3.2 Обработчик формы в JS
Добавьте в app.js:
// ========== ДОБАВЛЕНИЕ ПРОЦЕССА ==========
function handleAddProcess(event) {
event.preventDefault(); // Предотвращаем перезагрузку страницы
// Генерируем уникальный id
var maxId = 0;
processes.forEach(function(p) {
if (p.id > maxId) maxId = p.id;
});
var newId = maxId + 1;
// Собираем данные из формы
var newProcess = {
id: newId,
name: document.getElementById('inp-name').value,
type: document.getElementById('inp-type').value,
cycleTime: Number(document.getElementById('inp-cycleTime').value),
cost: Number(document.getElementById('inp-cost').value),
automation: Number(document.getElementById('inp-automation').value),
rework: Number(document.getElementById('inp-rework').value),
strategicValue: Number(document.getElementById('inp-strategicValue').value),
criticality: Number(document.getElementById('inp-criticality').value),
innovation: Number(document.getElementById('inp-innovation').value),
techDebt: Number(document.getElementById('inp-techDebt').value)
};
// Добавляем в массив
processes.push(newProcess);
// Обновляем отображение
renderAll();
// Очищаем форму
event.target.reset();
}
event.preventDefault() — зачем?
По умолчанию при отправке формы (submit) браузер перезагружает страницу. Метод preventDefault() отменяет это поведение, позволяя обработать данные в JavaScript без перезагрузки.
3.3 Удаление процесса
Добавьте обработчик кликов по кнопкам «Удалить»:
// ========== УДАЛЕНИЕ ПРОЦЕССА ==========
function handleDelete(event) {
// Проверяем, что клик был именно по кнопке удаления
if (event.target.classList.contains('btn-delete')) {
var id = Number(event.target.getAttribute('data-id'));
// Удаляем процесс из массива
processes = processes.filter(function(p) {
return p.id !== id;
});
renderAll();
}
}
Делегирование событий
Мы вешаем обработчик не на каждую кнопку, а на родительский элемент (<tbody>). Когда кнопка внутри таблицы нажата, событие «всплывает» до <tbody>, и мы проверяем event.target — была ли это кнопка удаления. Это называется делегирование событий (event delegation).
Преимущество: обработчик работает даже для динамически добавленных строк.
3.4 Сортировка по столбцам
// ========== СОРТИРОВКА ==========
var currentSort = { field: null, ascending: true };
function sortProcesses(field) {
// Если кликнули по тому же столбцу — меняем направление
if (currentSort.field === field) {
currentSort.ascending = !currentSort.ascending;
} else {
currentSort.field = field;
currentSort.ascending = true;
}
processes.sort(function(a, b) {
var valA, valB;
if (field === 'name' || field === 'type') {
valA = a[field];
valB = b[field];
if (valA < valB) return currentSort.ascending ? -1 : 1;
if (valA > valB) return currentSort.ascending ? 1 : -1;
return 0;
}
if (field === 'operational') {
valA = calcOperational(a);
valB = calcOperational(b);
} else if (field === 'strategic') {
valA = calcStrategic(a);
valB = calcStrategic(b);
} else if (field === 'zone') {
valA = getZone(a);
valB = getZone(b);
}
return currentSort.ascending ? valA - valB : valB - valA;
});
renderAll();
}
Обновите заголовки таблицы в index.html, чтобы они были кликабельными:
<thead>
<tr>
<th onclick="sortProcesses('name')">Название</th>
<th onclick="sortProcesses('type')">Тип</th>
<th onclick="sortProcesses('operational')">Опер. эфф.</th>
<th onclick="sortProcesses('strategic')">Стратег. важн.</th>
<th onclick="sortProcesses('zone')">Зона</th>
<th>Действия</th>
</tr>
</thead>
3.5 Фильтрация по зонам
Добавьте в секцию #controls в HTML:
<section id="controls">
<label>Фильтр по зоне:
<select id="zone-filter" onchange="filterByZone(this.value)">
<option value="all">Все зоны</option>
<option value="1">Зона 1 — Срочные изменения</option>
<option value="2">Зона 2 — Развитие</option>
<option value="3">Зона 3 — Стандартизация</option>
<option value="4">Зона 4 — Мониторинг</option>
</select>
</label>
</section>
В app.js:
// ========== ФИЛЬТРАЦИЯ ==========
var activeFilter = 'all';
function filterByZone(zoneValue) {
activeFilter = zoneValue;
renderAll();
}
Теперь модифицируйте renderTable и renderMatrix, чтобы они учитывали фильтр:
function getFilteredProcesses() {
if (activeFilter === 'all') return processes;
return processes.filter(function(p) {
return getZone(p) === Number(activeFilter);
});
}
Самостоятельная работа
Замените в renderTable и renderMatrix прямое обращение к processes на вызов getFilteredProcesses(). Например:
// Было:
processes.forEach(function(process) { ... });
// Стало:
getFilteredProcesses().forEach(function(process) { ... });
Это нужно сделать в обеих функциях.
3.6 Подключение обработчиков
Обновите секцию запуска в конце app.js:
// ========== ЗАПУСК ==========
document.addEventListener('DOMContentLoaded', function() {
// Подключаем форму добавления
document.getElementById('process-form')
.addEventListener('submit', handleAddProcess);
// Подключаем удаление через делегирование
document.getElementById('table-body')
.addEventListener('click', handleDelete);
// Первичная отрисовка
renderAll();
});
Чек-лист этапа 3
| # | Что проверить | Баллы |
|---|---|---|
| 3.1 | Форма добавления работает: процесс появляется в таблице и матрице | 2 |
| 3.2 | Кнопка «Удалить» удаляет процесс из таблицы и матрицы | 1 |
| 3.3 | Сортировка по столбцам работает (клик по заголовку) | 1 |
| 3.4 | Фильтр по зонам скрывает процессы из других зон | 1 |
| Итого за этап 3 | 5 |
Частые ошибки
- Форма перезагружает страницу → забыли
event.preventDefault() - Удаление не работает → обработчик повешен до создания элементов (используйте делегирование)
- Сортировка сбрасывается при добавлении → это нормально, если
renderAllвызывается без повторной сортировки - Фильтр не влияет на матрицу → забыли заменить
processesнаgetFilteredProcesses()вrenderMatrix