Задача
Написать страницу с асинхронными формами.
Данную задачу разработчику сайта приходится решать довольно часто.
Возникает вопрос: можно ли её решить раз и навсегда унифицированным способом без применения конструкторов и фрэймворков? Так чтобы все формы отправляли запросы без перезагрузки страницы и не конфликтовали между собой.
Проблемы
При написании обработчиков форм на чистом js у начинающего разработчика возникает множество вопросов:
как повесить обработчик события? На что его вешать? На форму или кнопку? Как вытащить из формы все значения? А если в форме есть селектор? А что делать если в форме инпут файла? Какие должны быть заголовки (headers)? Задавать ли enctype? Как отправить данные без перезагрузки? А если форма переместится в структуре документа – нужно будет переписывать обработчик? Наконец, как получить ответ от сервера и что с ним делать?
Желание
Написать один волшебный класс решающий все проблемы без необходимости писать код по безумному принципу Шайа Лабафа just do it или ещё проще как в сказке “js – по щучьему веленью, по моему прошенью”.
Чтобы всё работало независимо от того сколько на странице форм, чтобы у каждой формы был обработчик. И пусть будет неважно сколько полей у формы и какого они типа.
И без необходимости присваивать формам уникальные id.
Всё что нужно
Написать валидную html разметку
Подключить js-файл
И пусть оно само работает
Решение
И да, в какой то степени, это получилось сделать и уложить решение в примерно 60 строк кода.
Вот оно:
/*
те же действия для отдельных кнопок с классом
made by Erid Nord april 2022
*/
class Server {
static allForms;
constructor() {}
// Обработка всех форм
static main() {
this.allForms = document.querySelectorAll('form');
for (let element of this.allForms) {
this.formAddListener(element);
}
}
// Повесить обработчик на форму
static formAddListener(element) {
element.addEventListener(
'submit',
{ handleEvent: this.formEventListener,
server: this
}
);
}
// Обработчик формы
static async formEventListener(event) {
event.preventDefault();
let url = event.target.action;
let data = new FormData(event.target);
this.server.request(url, data).then(
result => this.server.formAnswer(result, event.target, '.report'),
error => this.server.formAnswer(error, event.target, '.report')
);
}
// Отправить данные по адресу
static async request(url, data = {}) {
let response = await fetch(
url,
{ method: 'POST',
body: data
}
);
return await response.text();
}
// разобрать ответ сервера
static formAnswer(result, form, selector = '.report') {
let block = form.parentElement.querySelector(selector);
if (!block) {
console.log(result, block);
return false;
}
block.innerHTML = result;
}
}
Server.main();
Тестирование:
Посмотреть как работает можно тут
Поместите этот скрипт к себе в js/forms.js
Просто подключите файл с этим скриптом к своей странице с аттрибутом defer
Обратите внимание, если один php обрабатывает несколько форм – просто задайте у этих форм скрытое поле с нужным вам значение аттрибута value – и по нему создавайте условие разбора запроса от формы.
Второй момент на который следует обратить внимание это вывод ответа от сервера. По умолчанию, он выводится в консоль браузера. Если нужно вывести его в html документ – то оберните форму в какой нибудь тег и внутри него рзместите тег с классом “report” – перед формой или после формы. Тогда ответ сервера попадёт в этот тег.
для проверки работы создайте страницу с такой html-разметкой:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="main.css">
<script defer src="js/forms.js"></script>
</head>
<body>
<div class="wrapper">
<div class="form_wrapper">
<div class="report">
</div>
<form action="/ajax/exec_form.php">
<input type="text" name="name" placeholder="название" autocomplete="off" />
<input type="text" name="property" placeholder="свойство" autocomplete="off" />
<input type="text" name="value" placeholder="значение" autocomplete="off" />
<input type="submit" value="Отправить" />
</form>
</div>
<div class="form_wrapper">
<div class="report">
</div>
<form action="/ajax/exec_form2.php">
<input type="hidden" name="page_action" placeholder="Событие" value="create" />
<input type="text" name="value" placeholder="доп параметр" autocomplete="off" />
<select name="category">
<option value="1">Холодильники</option>
<option value="2">Печи</option>
<option value="3">Мебель</option>
</select>
<input type="submit" value="Отправить" />
</form>
</div>
<div class="form_wrapper">
<div class="report">
</div>
<form action="/ajax/exec_form2.php">
<input type="text" name="property" placeholder="доп параметр" autocomplete="off" />
<input type="hidden" name="page_action" placeholder="Событие" value="upload" />
<input type="file" name="file" />
<input type="submit" value="Отправить" />
</form>
</div>
<div class="button_wrapper">
<ul>
<li><a href="/ajax/execute.php?id=2" class="button">кнопка 1</a></li>
<li><a href="/ajax/execute.php?id=5" class="button">кнопка 2</a></li>
<li><a href="/ajax/execute.php?id=12" class="button">кнопка 3</a></li>
<ul>
</div>
</div>
</body>
</html>
Не забудьте создать php скрипты на которые отправляете данные: /ajax/exec_form.php и /ajax/exec_form2.php.
В качестве кода этих скриптов можете вставить следующие отладочные заглушки:
<?
echo 'Обработчик ' . basename($_SERVER['PHP_SELF']) . ' получил данные: ';
echo '<pre>';
print_r($_POST);
if (!empty($_FILES)) {
echo '<p>Переданные файлы</p>';
print_r($_FILES);
}
echo '</pre>';
?>
Пока не реализовано:
- возврат json
- изменение селектора для ответа
- вывод ошибок по конкретным полям
- работа с отдельными ссылками, не формами
- разные действия при получении ответа, например скрытие формы, замена формы ответом или блоком или добавление ответа к списку или таблице