
FetchIt
Легковесная AJAX-отправка форм в MODX на Fetch API поверх FormIt или своего сниппета


a11y-dialog — маленькая (около 2 КБ) библиотека модальных окон, сделанная по рекомендациям WAI-ARIA. Фокус не уходит из окна, Esc и клик по фону его закрывают, а после закрытия фокус возвращается на кнопку, которая окно открыла. Своих стилей у библиотеки нет — окно выглядит так, как вы его оформите.
<script src="https://cdn.jsdelivr.net/npm/a11y-dialog@8/dist/a11y-dialog.min.js" defer></script><button type="button" data-a11y-dialog-show="callback">Заказать звонок</button>
<div class="dialog" id="callback" aria-labelledby="callback-title" aria-hidden="true">
<div class="dialog__overlay" data-a11y-dialog-hide></div>
<div class="dialog__content" role="document">
<button type="button" class="dialog__close" data-a11y-dialog-hide aria-label="Закрыть">×</button>
<h2 id="callback-title">Заказать звонок</h2>
<form action="[[~[[*id]]]]" method="post">
<label> Имя
<input type="text" name="name" value="[[+fi.name]]" autocomplete="name">
<span data-error="name">[[+fi.error.name]]</span>
</label>
<label> Телефон
<input type="tel" name="phone" value="[[+fi.phone]]" autocomplete="tel">
<span data-error="phone">[[+fi.error.phone]]</span>
</label>
<div role="alert" data-validation-error style="display: none;"></div>
<button type="submit">Отправить</button>
</form>
</div>
</div><button type="button" data-a11y-dialog-show="callback">Заказать звонок</button>
<div class="dialog" id="callback" aria-labelledby="callback-title" aria-hidden="true">
<div class="dialog__overlay" data-a11y-dialog-hide></div>
<div class="dialog__content" role="document">
<button type="button" class="dialog__close" data-a11y-dialog-hide aria-label="Закрыть">×</button>
<h2 id="callback-title">Заказать звонок</h2>
<form action="{$_modx->resource.id | url}" method="post">
<label> Имя
<input type="text" name="name" value="{$_modx->getPlaceholder('fi.name')}" autocomplete="name">
<span data-error="name">{$_modx->getPlaceholder('fi.error.name')}</span>
</label>
<label> Телефон
<input type="tel" name="phone" value="{$_modx->getPlaceholder('fi.phone')}" autocomplete="tel">
<span data-error="phone">{$_modx->getPlaceholder('fi.error.phone')}</span>
</label>
<div role="alert" data-validation-error style="display: none;"></div>
<button type="submit">Отправить</button>
</form>
</div>
</div>a11y-dialog может сам создать окна по атрибуту data-a11y-dialog, но тогда до экземпляра окна не добраться, и закрыть его из кода нельзя. Поэтому окна создаются вручную:
const dialogs = new Map()
document.addEventListener('DOMContentLoaded', () => {
for (const element of document.querySelectorAll('.dialog')) {
dialogs.set(element, new A11yDialog(element))
// Окно закрывают — сбросить форму, чтобы в следующий раз она была чистой
element.addEventListener('hide', () => {
element.querySelector('form[data-fetchit]')?.reset()
})
}
})
// Форма принята — закрыть её окно
document.addEventListener('fetchit:success', ({ detail: { form } }) => {
dialogs.get(form.closest('.dialog'))?.hide()
})Событие hide a11y-dialog отправляет на элемент окна, и наверх оно не всплывает, поэтому обработчик вешается на каждое окно.
Минимум стилей, без которых окно не спрячется и не встанет поверх страницы:
.dialog {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
}
.dialog[aria-hidden='true'] {
display: none;
}
.dialog__overlay {
position: fixed;
inset: 0;
background: rgb(15 23 42 / 0.5);
}
.dialog__content {
position: relative;
width: min(100% - 2rem, 28rem);
padding: 1.5rem;
border-radius: 1rem;
background: #fff;
}
.dialog__close {
position: absolute;
top: 0.75rem;
right: 0.75rem;
}