Ещё одна статья-туториал, опубликованная сначала на habr.com, о создании форм обратной связи на Joomla 3. В статье описывается подход к созданию форм, работающий как для Joomla 3, так и для Joomla 4 и последующих версий.
Преамбула
В современном интернет-магазине должно быть немало форм обратной связи. Интернет-маркетологи стараются предупредить все возможные ситуации и порой создают формы обратной связи чуть ли ни на каждый потенциальный интент пользователя.
Оставив за скобками традиционные "заказать звонок", "подписаться на новости" привожу примеры форм интернет-магазина/каталога, которые мне доводилось встречать на практике при работе с Joomla 3:
- категория (листинг) товаров - "купить в 1 клик"
- категория (листинг) товаров - "запрос цены" (если у товара не указана цена)
- категория (листинг) товаров - "задать вопрос"
- карточка товара - "купить в 1 клик"
- карточка товара - "запрос цены" (если у товара не указана цена)
- карточка товара - "задать вопрос"
- карточка товара - "получить скидку" или "нашли дешевле?"
- карточка товара - "сделать индивидуальный заказ"
- карточка товара - "заказать оптом"
…и т.д. и т.п. Чудный мир фантазий интернет-маркетолога (безусловно опирающийся на данные аналитики, мониторинг конкурентов и рынка в целом, профессиональное чутьё и опыт) предлагает самые разные варианты ответа на вопрос: "как достать клиента?"
Задача
Создать N-е количество форм обратной связи для интернет-магазина на базе Joomla. Формы должны быть красивыми и информативными, не вызывать неточного толкования у посетителя в том, что он делает. В форму выводим информацию о товаре и интент пользователя.
В форме должен меняться заголовок ("Задать вопрос", "узнать цену" и т.д.), картинка товара, его цена, название и артикул.
К каким проблемам это приводит?
Мы постоянно слышим о том, что что-то можно сделать "легко и быстро", однако забываем о том, что для этого нужны знания и опыт. Везде.
В Joomla формы обратной связи - это модули, зачастую с огромным количеством настроек (Shack Forms, например). Или же компонент + модуль (RS Forms и другие).
Чаще всего один модуль имеет только одну настройку темы письма (ручками указываем "задать вопрос", "купить оптом"). В итоге получается, что мы на каждый интент пользователя создаем отдельный модуль. Сверяемся со списком форм выше и получаем 9 однотипных модулей только для каталога товаров. Значит мы получаем много мусора в коде страницы и все вытекающие отсюда минусы.
Иной раз СЕО-специалисты (ничего личного) делают зоопарк форм обратной связи не модулями Joomla, а сторонним конструктором форм - DSForms, например. А тут на основной почте сайта поменяли пароль.... И человек меняет в 9 формах пароль. Это рутина, этим вообще некогда заниматься и в 2-3 формах остается старый пароль. Через несколько дней (хорошо если!) звонит разгневанный клиент: "У нас просели заявки!" Мы начинаем смотреть логи, отправлять тестовые заявки, искать почему так произошло, ведь разработчику никто не сказал, что меняли пароль...
Форма обратной связи для Joomla своими руками
Ну, почти своими. Описываемый ниже подход подойдет для любых форм обратной связи. И отдельный акцент мы делаем на формах, в которых нужно указать информацию о товаре.
Замена модулей обратной связи на плагин-обработчик
Плагин Radical Form - плагин-обработчик формы обратной связи. На странице документации плагина описаны причины появления такого решения и в целом я соглашаюсь с доводами.
Если кратко, то обычно каждое стороннее расширение тянет за собой js-скрипты, css, которые далеко не всегда вписываются в контекст сайта. В итоге всё равно приходится делать свой макет вывода. А если автор стороннего модуля решил сделать решения в духе No code (то есть максимум настроек внешнего вида в админке), то для разработчика работа с таким решением может превратиться в ад.
С Radical Form становится возможным следующий подход в работе:
- вы делаете свой HTML формы (вообще любой!),
- кнопке "отправить" назначается класс
rf-button-send
- код формы размещаете в модуле типа "HTML-код" везде, где это необходимо.
И оно работает. Именно это решение мы разовьём в тексте ниже. Так же рекомендую ознакомиться с документацией по Radical Form для лучшего понимания общей картины.
Несколько форм в одной
Чаще всего формы показываются в модальных (всплывающих) окнах. Я в основном работаю с сайтами в стеке Bootstrap, поэтому примеры HTML будут приведены для него. Однако, в контексте данной статьи не имеет значение CSS-фреймворк.
По сути все перечисленные выше формы - это одна форма, в которой нужно менять только заголовок модального окна и тему письма, а код формы может быть одним и тем же. Для этого нам потребуется раздел Varying modal content из документации Bootstrap 4.6, для Bootstrap 5.3.
Обратите внимание, что для Bootstrap 5+ имена data-атрибутов поменялись с data-toggle
, на data-bs-toggle
, data-target
на data-bs-target
и так далее.
<!-- data-form-title - здесь указываем заголовок модального окна и тему письма -->
<button class="btn btn-primary" type="button" data-toggle="modal" data-target="#exampleModal2" data-form-title="Обратный звонок">Обратный звонок</button>
<button class="btn btn-primary" type="button" data-toggle="modal" data-target="#exampleModal2" data-form-title="Заказать консультацию">Заказать консультацию</button>
<button class="btn btn-primary" type="button" data-toggle="modal" data-target="#exampleModal2" data-form-title="Запрос скидки">Запрос скидки</button>
<div id="exampleModal2" class="modal fade" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="exampleModalLabel" class="modal-title">Здесь будет заголовок из атрибута data-form-title кнопки</h5>
<button class="close" type="button" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button></div>
<div class="modal-body">
<form>
<div class="form-group mb-4">
<label for="phone3">Ваш телефон *</label>
<input id="phone3" class="form-control required" name="PHONE" required="" type="tel" placeholder="+79999999999" aria-describedby="PhoneHelp" />
</div>
<div class="custom-control custom-switch my-3">
<input id="agreement_gdpr" class="custom-control-input required" name="gdpr" required="" type="checkbox" />
<label class="custom-control-label" for="agreement_gdpr">Я принимаю <a href="#" rel="noindex">политику обработки персональных данных</a>.</label>
</div>
<input name="rfSubject" value="Скрытое поле для индивидуальной темы письма из каждой формы" type="hidden" />
<button class="btn btn-lg bg-dark text-white rf-button-send" type="submit"><i class="fa fa-paper-plane-o" aria-hidden="true"></i> Отправить</button>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$('#exampleModal2').on('show.bs.modal', function (event) {
let button = $(event.relatedTarget); // Кнопка, вызвавшая модальное окно
let formTitle= button.data('form-title'); // Получаем информацию из data-* атрибута
// Обновляем информацию в модальном окне.
let modal = $(this);
// Устанавливаем заголовок модального окна
modal.find('.modal-title').text(formTitle);
// Устанавливаем тему письма для писем, отправляемых из формы.
modal.find('.modal-body [name=rfSubject]').val(formTitle);
})
</script>
Пример javascript-кода без использования jQuery (для Bootstrap 5+)
Bootstrap 5 отказался от использования библиотеки jQuery. Следуя этой тенденции привожу пример кода без использования jQuery.
(() => {
// Работаем после полной загрузки страницы.
document.addEventListener('DOMContentLoaded',() => {
// Получаем наше модальное окно
const contactFormModal = document.getElementById('contact_form_modal');
if (contactFormModal) {
contactFormModal.addEventListener('show.bs.modal', event => {
// Кнопка, вызвавшая модальное окно
const button = event.relatedTarget;
// Получаем инфу из data-* атрибутов
const formTitle = button.getAttribute('data-form-title');
const formSubTitle = button.getAttribute('data-form-subtitle');
// Обновляем информацию в модальном окне.
const modalTitle = contactFormModal.querySelector('.modal-title');
const modalBodyInput = contactFormModal.querySelector('.modal-body [name=rfSubject]');
console.log(modalBodyInput);
// Устанавливаем заголовок модального окна
modalTitle.textContent = formTitle;
// Устанавливаем тему письма для писем, отправляемых из формы.
modalBodyInput.value = formTitle;
});
}
});
})();
В data-атрибуты кнопки можно поместить самую разную информацию. В некоторых случаях я помещаю туда нужные для отображения поля формы (name, email, phone, comment), какие из них должны быть обязательными для заполнения и т.д. Например, получение обязательных полей из атрибута data-fields-required
кнопки, имена которых указаны через запятую:
let fieldsRequired = button.data('fields-required');
if (typeof fieldsRequired !== 'undefined'){
fieldsRequired = fieldsRequired.split(',');
$(fieldsRequired).each(function(index){
$('.contact_form_modal_'+fieldsRequired[index]+'_group .form-control').attr('required','required');
});
}
Пример этого же javascript-кода без использования jQuery (для Bootstrap 5+).
Обратите внимание на то, что в случае использования jQuery мы использовали метод $('#elem').data('attr-name');
. Когда мы пишем на чистом javascript нам нужно указывать полностью имя атрибута: data-fields
, data-fields-required
.
let fields = button.getAttribute('data-fields');
if (typeof fields !== 'undefined'){
fields = fields.split(',');
fields.forEach(index => {
let field = contactFormModal.querySelector('.contact_form_modal_' + index + '_group');
field.style.removeProperty('display');
});
}
let fieldsRequired = button.getAttribute('data-fields-required');
if (typeof fieldsRequired !== 'undefined'){
fieldsRequired = fieldsRequired.split(',');
fieldsRequired.forEach(index => {
let field = contactFormModal.querySelector('.contact_form_modal_'+index+'_group .form-control');
field.setAttribute('required','required');
});
}
Подробно кейс создания множества форм в одной с примерами форм и кода был разобран здесь: Интеграция форм обратной связи и Битрикс 24 на сайте Joomla.
Таким образом мы размещаем в коде сайта только 1 модуль с HTML-кодом формы обратной связи и в любом нужном нам месте кнопки для вызова модального окна с нужными data-атрибутами. А создание новой формы сводится к копированию кода кнопки и указанию нужных data-атрибутов. Также помним о том, что для Bootstrap 5+ data-атрибуты поменялись с data-*
на data-bs-*
.
<button class="btn btn-sm contact_form_modal_btn" data-toggle="modal" data-target="#contact_form_modal" data-form-title="Купить в 1 клик" data-form-subtitle="Вас интересует товар" data-vm-product-id="16869" data-fields="name,phone" data-fields-required="phone">Купить в 1 клик</button>
Получение информации о товаре для формы обратной связи
Для красивого модального окна нам нужно брать информацию о товаре: картинку, название товара, артикул, цену.
Первый путь, совсем неправильный
Получение данных из HTML-верстки. Список товаров (категория). Разработчик в js-скрипте "стучится" в карточку товара, где указан класс или id с id или артикулом товара у родительского элемента. Таким образом можно получить путь картинки из <img src="/..." class="product_image"/>
или название товара из <span class="product_name">
.
Таким образом работает, например, ручная разметка целей аналитики мышкой в Яндекс.Метрике и Google Analytics. Если Вы поменяете HTML-разметку - проверьте работает ли отправка целей.
Огромный минус этого подхода в том, что если меняется дизайн и нарушается вложенность элементов - формы обратной связи "отваливаются" и нужно обновлять js-код.
Второе - если у сайта нет постоянного разработчика, то каждому следующему нужно разбираться в коде предыдущего. А далеко не все "чистят за собой" и порой в HTML-коде страницы видны "следы" нескольких разработчиков.
Второй путь, тоже неправильный
Приведу в качестве примера магазин на Virtuemart 3. Шаблоны дизайна для этого магазина делаются с помощью переопределений Joomla (хороший мануал по шаблонам Virtuemart 3 и список макетов). То есть файлы, где описана вёрстка, лежат в папке templates/ваш_шаблон/html/com_virtuemart/
. В качестве подопытного кролика у нас будет список товаров в категории.
Передача данных из php в javascript в Joomla
Дело в том. что в Joomla есть простой и удобный способ передачи данных из php в javascript.
use Joomla\CMS\Factory;
//Массив данных о товарах Virtuemart
$virtuemart_products = array();
// Добавление json-объекта с данными
Factory::getDocument()->addScriptOptions('virtuemart_products_details',$virtuemart_products);
Данные передаются в виде json-объекта, доступ к которому с фронта получаем через функцию Joomla.getOptions('virtuemart_products_details ')
. Это стандартная функция Joomla, никаких дополнительных плагинов для этого устанавливать не нужно.
Для этого мы идём в файл templates/ваш_шаблон/html/com_virtuemart/category/default.php
и циклом добавляем нужные нам данные о товарах Virtuemart в отдельный массив.
<?php
use Joomla\CMS\Factory;
$doc = Factory::getDocument();
//Данные товаров для форм обратной связи на Radical Form.
// Доступ из js через let vm_products_data = Joomla.getOptions('virtuemart_products_details');
// Каркас формы находится в модуле типа HTML-код. Там же js-обработка.
$vm_products_for_contact_form = array();
foreach($this->products as $product){
//4-й параметр true возвращает только цену с валютой без HTML-обёртки
$vm_price = $this->currency->createPriceDiv ('salesPrice', 'COM_VIRTUEMART_PRODUCT_SALESPRICE', $product->prices, true);
$vm_products_for_contact_form[$product->virtuemart_product_id]['vm_product_name'] = $product->product_name;
$vm_products_for_contact_form[$product->virtuemart_product_id]['vm_product_price'] = $vm_price;
$vm_products_for_contact_form[$product->virtuemart_product_id]['vm_product_image_url'] = $product->file_url_thumb;
}
$doc->addScriptOptions('virtuemart_products_details',$vm_products_for_contact_form);
?>
А затем получаем данные на фронте в js:
//Из документации Bootstrap - получаем последнюю нажатую кнопку
let button = $(event.relatedTarget);
// Заголовок модального окна и тема письма из data-атрибута
let formTitle = button.data('form-title');
// Id товара - ключ к массиву с данными
let vm_product_sku = button.data('vm-product-id');
let virtuemart_products_details = Joomla.getOptions('virtuemart_products_details');
let vm_product_name = virtuemart_products_details[vm_product_sku]['vm_product_name'];
let vm_product_price = virtuemart_products_details[vm_product_sku]['vm_product_price'];
let vm_product_image_url = virtuemart_products_details[vm_product_sku]['vm_product_image_url'];
Js-скрипт при клике на кнопку "купить в 1 клик" (например) из data-атрибутов считывает id товара и затем обращается в массив с данными, где id товара - ключ к получению значений. А дальше - дело техники - полученные данные подставляем в HTML-код формы обратной связи.
Для карточки товара аналогичные операции проводим с файлом templates/ваш_шаблон/html/com_virtuemart/productdetails/default.php
use Joomla\CMS\Factory;
/**
* Добавляем опции для скрипта форм обратной связи с данными товара
* - название
* - артикул
* - цена
* - url миниатюры изображения
*
* HTML-каркас формы находится в модуле типа HTML-код. Обработчик - плагин Radical Form.
* Поля в форме скрываются/показываются динамически через js, который находится в том же модуле.
* Заголовки модального окна, темы письма, подзаголовок берутся из data-атрибутов кнопки.
*/
$doc = Factory::getDocument();
$vm_price = $this->currency->createPriceDiv ('salesPrice', 'COM_VIRTUEMART_PRODUCT_SALESPRICE', $this->product->prices, true);
$vm_image = $this->product->images[0]->file_url_thumb;
$doc->addScriptOptions('virtuemart_products_details',array($this->product->virtuemart_product_id => array(
'vm_product_name' => $this->product->product_name,
'vm_product_price' => $vm_price,
'vm_product_image_url' => $vm_image
)
));
Почему этот путь тоже неправильный?
Потому, что функционал привязан к дизайну. При смене дизайна рискуем потерять функционал. Не дай Бог на сайте не сделаны переопределения и правится стандартный шаблон магазина - при обновлении ядра все хаки затираются и работа проводится заново.
Логика сайта должна быть отделена от отображения. Поэтому все подобные ситуации оформляются в Joomla в виде плагинов. Системные плагины (группа system) срабатывают всегда и раньше, нежели плагины конкретных групп. Плагины групп срабатывают в определенном компоненте при определенных условиях. Если Вы знаете, что Ваш плагин должен работать только в одном конкретном месте - укажите ему группу плагинов. Это сэкономит ресурсы сервера, так как Ваш код не будет исполняться при каждом шевелении, а только в тот момент, когда он действительно необходим.
Третий путь, правильный - создание плагина
Приведем в качестве примера другой компонент интернет-магазина на Joomla - JoomShopping. Задача стоит та же самая. JoomShopping богат на триггеры для плагинов. Как создать плагин для Joomla - читаем документацию. Это не так уж сложно.
Также Вам помогут мои статьи:
- Создание плагинов с учётом новой структуры Joomla 4
- Распространенные ошибки при написании плагинов Joomla 4
Плагин создаем в группе Jshoppingproducts
, таким образом не будет попытки выполнить наш код при просмотре статьей, страниц с контактами, при переходах в админке и т.д. Он будет работать только там, где надо. Так же наш код, будучи самостоятельным плагином, не пострадает при обновлении Joomla, при обновлении компонента магазина.
Для карточки товара мы находим событие onAfterDisplayProduct
, куда приходит объект с данными товара.
use Joomla\CMS\Factory;
public function onAfterDisplayProduct(&$product)
{
$jShopConfig = JSFactory::getConfig();
$product_info = array();
$product_info['product_name'] = $product->name;
$product_info['product_image_url'] = $jShopConfig->image_product_live_path.'/'.$product->image;
$product_info['ean'] = $product->product_ean;
$product_info['old_price'] = formatprice($product->product_old_price);
$product_info['price'] = formatprice($product->product_price);
$product_info['delivery_time'] = $product->delivery_time;
// Нужно для того, чтобы не переписывать js-код снаружи. Так же стучимся по id товара
$product_array = array(
$product->product_id => $product_info
);
$doc = Factory::getDocument();
$doc->addScriptOptions('jshop_products_details',$product_array);
}
Для категории товаров используем событие onBeforeDisplayProductListView
, куда приходит $view
целиком и список товаров отдельно $productlist
.
Результат работы плагина будет таким:
Готовый плагин для добавления данных товара в json-объект для JoomShopping можно скачать здесь.
Updated: с версии 1.1.1 плагин поддерживает Joomla 4. В плане работы плагина принципиальной разницы между Joomla 3 и Joomla 4 нет.
Добавлены параметры в настройку плагина:
Пример JavaScript кода для получения данных во фронтенде
Это пример кода с использованием jQuery с одного из моих проектов. Не забудьте адаптировать данный код под свои нужды.
(function($) {
$('#contact_form_modal').on('shown.bs.modal', function (event) {
let button = $(event.relatedTarget);
let formTitle = button.data('form-title');
let formSubTitle = button.data('form-subtitle');
let product_id = button.data('product-id');
let comments = '';
if (typeof product_id !== 'undefined'){
let jshop_products_details = Joomla.getOptions('jshop_products_details');
let product_name = jshop_products_details[product_id]['product_name'];
let product_price = jshop_products_details[product_id]['price'];
let product_image_url = jshop_products_details[product_id]['product_image_url'];
let product_ean = jshop_products_details[product_id]['ean'];
if (jQuery.isEmptyObject(jshop_products_details[product_id]) !== true) {
comments = comments + formSubTitle + ' ' + product_name + ", код " + product_ean;
formSubTitle = "<span>" + formSubTitle + " <strong>" + product_name + ", код " + product_ean;
if(product_image_url){
let subTitleImg = "<img src=\"" + product_image_url +"\" width=\"auto\" height=\"64px\" class=\"me-2\" /> ";
formSubTitle = subTitleImg + formSubTitle;
}
if(product_price){
product_price = " за " + product_price;
comments = comments + product_price;
formSubTitle = formSubTitle + product_price;
}
formSubTitle = formSubTitle + "</strong></span>";
//Запрос о скидки
if(formTitle == 'Запрос скидки'){
comments = 'Я хочу получить скидку на ' + product_name +' код' + product_ean + ' за '+ product_price + ' потому, что ...';
}
}
}//if typeof product_id !== 'undefined'
let fields = button.data('fields');
if (typeof fields !== 'undefined'){
fields = fields.split(',');
$(fields).each(function(index){
$('.contact_form_modal_' + fields[index] + '_group').show();
});
}
let fieldsRequired = button.data('fields-required');
if (typeof fieldsRequired !== 'undefined'){
fieldsRequired = fieldsRequired.split(',');
$(fieldsRequired).each(function(index){
$('.contact_form_modal_'+fieldsRequired[index]+'_group .form-control').attr('required','required');
});
}
let modal = $(this);
modal.find('.modal-title').text(formTitle);
modal.find('.modal-subtitle').html(formSubTitle);
modal.find('.modal-body input[name=rfSubject]').val(formTitle);
modal.find('.modal-body [name=comments]').val(comments);
console.log(comments);
});//on('show.bs.modal'
$('#contact_form_modal #agreement_gdpr').click(function() {
if ($('#contact_form_modal #agreement_gdpr').is(':checked')) {
$('#contact_form_modal .rf-button-send').removeAttr("disabled", "disabled").fadeTo("slow",1);
}
else
{
$('#contact_form_modal .rf-button-send').attr("disabled", "disabled").fadeTo("slow",0.4);
}
});
$('#contact_form_modal').on('hide.bs.modal', function (event) {
$('.form-group').hide();
$('.form-group .form-control').removeAttr('required','required');
$('#contact_form_modal').trigger('reset');
$('#contact_form_modal .is-invalid').removeClass('is-invalid');
});
})(jQuery);
Пример Javascript-кода без использования jQuery
Так же не забудьте адаптировать код под реалии своего шаблона. Это пример кода для категории товаров.
(() => {
document.addEventListener('DOMContentLoaded', () => {
let buy_one_click_buttons = document.querySelectorAll('.buy-one-click');
for (let i = 0, l = buy_one_click_buttons.length; l > i; i++) {
// Listen for click event
buy_one_click_buttons[i].addEventListener('click', event => {
event.preventDefault();
const {
target
} = event;
let product_id = target.getAttribute('data-product-id');
if (typeof product_id !== 'undefined'){
let jshop_products_details = Joomla.getOptions('jshop_products_details');
let product_name = jshop_products_details[product_id]['product_name'];
let product_ean = jshop_products_details[product_id]['ean'];
let product_price = jshop_products_details[product_id]['price'];
if(typeof product_price === 'undefined'){
product_price = '';
}
let product_image_url = jshop_products_details[product_id]['product_image_url'];
let product_image = "";
if(product_image_url){
product_image = "<img src=\"" + product_image_url +"\" width=\"auto\" height=\"64px\" class=\"me-2\" /> ";
}
console.log(product_image);
let input_rfSubject = document.querySelector('#fast-order input[name=rfSubject]');
input_rfSubject.value = target.getAttribute('data-form-title');
let display_product_name = document.querySelector('#fast-order .product_name');
display_product_name.innerText = product_name;
let display_product_price = document.querySelector('#fast-order .price');
display_product_price.innerText = product_price;
let display_product_ean = document.querySelector('#fast-order .ean');
display_product_ean.innerText = product_ean;
let display_product_image = document.querySelector('#fast-order .product_image_url');
display_product_image.innerHTML = product_image;
let comments = 'Меня интересует товар ' + product_name + ' за ' + product_price + ', артикул ' + product_ean;
let input_comments = document.querySelector('#fast-order input[name=comments]');
input_comments.value = comments;
}
});
}
});
})();
Ещё один пример javascript кода. В целом в нём происходит всё то же самое.
(() => {
document.addEventListener('DOMContentLoaded', () => {
const contactFormModal = document.getElementById('contact_form_modal');
if (contactFormModal) {
contactFormModal.addEventListener('show.bs.modal', event => {
// Кнопка, вызвавшая модальное окно
const button = event.relatedTarget;
// Получаем инфу из data-* атрибутов
const formTitle = button.getAttribute('data-form-title');
const formSubTitle = button.getAttribute('data-form-subtitle');
// Обновляем информацию в модальном окне.
const modalTitle = contactFormModal.querySelector('.modal-title');
const modalBodyInput = contactFormModal.querySelector('.modal-body [name=rfSubject]');
// Устанавливаем заголовок модального окна
modalTitle.textContent = formTitle;
// Устанавливаем тему письма для писем, отправляемых из формы.
modalBodyInput.value = formTitle;
let fields = button.getAttribute('data-fields');
if (typeof fields !== 'undefined') {
fields = fields.split(',');
fields.forEach(index => {
let field = contactFormModal.querySelector('.contact_form_modal_' + index + '_group');
field.style.removeProperty('display');
});
}
let fieldsRequired = button.getAttribute('data-fields-required');
if (typeof fieldsRequired !== 'undefined') {
fieldsRequired = fieldsRequired.split(',');
fieldsRequired.forEach(index => {
let field = contactFormModal.querySelector('.contact_form_modal_' + index + '_group .form-control');
field.setAttribute('required', 'required');
});
}
});
}
});
})();