Преамбула
В современном интернет-магазине должно быть немало форм обратной связи. Интернет-маркетологи стараются предупредить все возможные ситуации и порой создают формы обратной связи чуть ли ни на каждый потенциальный интент пользователя.
Оставив за скобками традиционные "заказать звонок", "подписаться на новости" привожу примеры форм интернет-магазина/каталога, которые мне доводилось встречать на практике при работе с 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.1.
<!-- 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>
В 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');
});
}
Подробно кейс создания множества форм в одной с примерами форм и кода был разобран здесь: Интеграция форм обратной связи и Битрикс 24 на сайте Joomla.
Таким образом мы размещаем в коде сайта только 1 модуль с HTML-кодом формы обратной связи и в любом нужном нам месте кнопки для вызова модального окна с нужными data-атрибутами. А создание новой формы сводится к копированию кода кнопки и указанию нужных data-атрибутов.
<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">.
Огромный минус этого подхода в том, что если меняется дизайн и нарушается вложенность элементов - формы обратной связи "отваливаются" и нужно обновлять 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 - читаем документацию. Это не так уж сложно.
Плагин создаем в группе 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 можно скачать здесь. Добавлены параметры в настройку плагина:
Заключение
Цель данной статьи была поделиться своим опытом в работе с различными магазинами на Joomla. Возможно, он кому-то будет полезен. Возможно, кто-то предложит другие, альтернативные пути решения той же задачи. Предложения и улучшения приветствуются в комментариях.
Комментарии (6)
kokgreat
20.09.2021 11:31Подскажте, через такую реализацию (Radical Form) можно подключить Капчу стандартную?
Или как вообще защититься от спама через такую реализацию?sergeytolkachyov Автор
20.09.2021 11:36Поскольку фронт формы Вы собираете по сути сами - можете подключать какую угодно капчу. Дело в том, что в случае с Radical Form CSRF-токена в коде формы нет. Боты даже если сабмитят форму, то токен не получают. Поэтому спама через Radical Form меньше. Как вариант - можно не оборачивать форму в тег form, хотя это уже нарушение стандартов, но вроде такое решение тоже будет работать. Так же слышал, что боты (хрумер и т.д.) ещё не осилили hcaptcha. Попробуйте её.
В телеграме есть чат русскоязычного сообщества Joomla, в котором есть разработчик Radical Form. По ней он сможет ответить на все вопросы.sergeytolkachyov Автор
20.09.2021 13:38Не корректно выразился. Токен есть, но он появляется после загрузки страницы. Поэтому боты токен не получают
gresserg
Интересно и поучительно. Спасибо!
sergeytolkachyov Автор
Спасибо :-)