Июл
23
2021
1

Как сделать форму в Битрикс со своей версткой и Ajax режимом

Покажу как сделать в 1С-Битрикс форму обратной связи, как её настроить, чтобы принимать лиды.

Форма будет называться "Оставьте заявку". Наша обратная форма Битрикс будет содержать поля имя, телефон, сообщение.

Будет использоваться модуль bitrix webforms.

Создание и настройка формы в административном разделе

Прежде, чем приступать, рекомендую выключить упрощенный режим редактирования форм в настройках модуля Вебформы. И сохранить настройку, соответственно.

В противном случае, бывают баги, по типу "не отправляются сообщения из формы на почту", "не привязывается почтовый шаблон к форме".

Теперь идем в Сервисы -> Веб формы -> Настройка форм и жмем там Создать, чтобы создать форму.

Во вкладке Свойства заполняем все необходимые поля. Во вкладке "Доступ" проверьте, чтобы для группы "Все пользователи" было право "Заполнение формы".

Капчу на данном этапе включать не будем.

Жмем Применить, чтобы создать форму.

Переходим во вкладку Вопросы и добавляем их.

Настройка полей для формы:

  • Имя:
    • Вкладка Свойства
      • Активность: да
      • Символьный идентификатор: "NAME"
      • Обязателен: да
    • Вкладка Вопрос
      • Заголовок: "Ваше имя"
    • Вкладка Ответ
      • Текст [ANSWER_TEXT]: ставим пробел
      • Значение [ANSWER_VALUE]: оставляем пустым
      • Тип поля: "text"
      • Параметры: пусто
  • Телефон
    • Вкладка Свойства
      • Активность: да
      • Символьный идентификатор: "PHONE"
      • Обязателен: да
    • Вкладка Вопрос
      • Заголовок: "Номер телефона"
    • Вкладка Ответ
      • Текст [ANSWER_TEXT]: ставим пробел
      • Значение [ANSWER_VALUE]: оставляем пустым
      • Тип поля: "text"
      • Параметры: "phone"
  • Сообщение
    • Вкладка Свойства
      • Активность: да
      • Символьный идентификатор: "MESSAGE"
      • Обязателен: нет
    • Вкладка Вопрос
      • Заголовок: "Сообщение"
    • Вкладка Ответ
      • Текст [ANSWER_TEXT]: ставим пробел
      • Значение [ANSWER_VALUE]: оставляем пустым
      • Тип поля: "textarea"
      • Параметры: "phone"

Так же создаем статус по умолчанию для результатов, если он не создался.

Заголовок: Default. Во вкладке Доступ для всех операций поставьте "Создатель результата".

В панели администратора всё добавили и сохранили, молодцы!

Вывод формы в публичном разделе сайта

Если вы хотите разместить форму на отдельной странице, создайте страницу для формы.

А если хотите разместить в шаблоне, например в шапке, тогда создайте временную страницу, чтобы настраивать форму там. После скопируете вызов компонента формы с временной страницы. Я создам файл test.php.

Открываем редактирование страницы в визуальном редакторе, в компонентах ищем form.result.new, перетягиваем в окно правки.

Если нет поиска компонентов, то нажмите на кнопку у правой границы редактора.

Через несколько секунд появится окно настройки компонента.

Там нам надо заполнить:

  • ID веб-формы - выбрать нашу форму
  • Использовать расширенный вывод сообщений об ошибках - да
  • Включить поддержку ЧПУ - нет
  • Страница со списком результатов - пусто
  • Страница редактирования результата - пусто
  • Страница с сообщением об успешной отправке - пусто

После сохранения компонента, на странице появится форма. Она не очень красивая, поэтому установим свой шаблон.

Включим режим правки, наведем курсор на форму, там скопируем шаблон, как показано на картинке.

Укажем новое название шаблона, шаблон сайта - текущий.

В шаблоне компонента сразу удалим все лишние файлы. Если их не удалить, в будущем они будут создавать путаницу при доработке шаблона, потому что будем иметь полно файлов не понятного назначения. А еще сайт будет тяжелый, так как пользователь будет получать много не нужных скриптов и стилей.

Оставим только файл template.php

Приступим к редактированию шаблона.

В template.php доступны следующие переменные:

Служебные:

  • $arResult["isFormErrors"] - равен "Y", если есть ошибки с формой
  • $arResult["isFormNote"] - равен "Y", если форма успешно отправлена. С условием по этой переменной можно выводить сообщение об успешной отправке уже без формы.
  • $arResult["FORM_HEADER"] - открытие формы и системные теги
  • $arResult["FORM_FOOTER"] - закрытие формы и системные теги
  • $arResult["QUESTIONS"] - массив вопросов формы
  • $arResult["isUseCaptcha"] - равно true, если включен показ капчи
  • $arResult["CAPTCHA_IMAGE"] - тег img с картинкой капчи
  • $arResult["CAPTCHA_FIELD"] - поле для ввода капчи

Информация пользователю:

  • $arResult["FORM_ERRORS_TEXT"] - текст ошибок
  • $arResult["FORM_NOTE"] - сообщение об успешной отправке
  • $arResult["FORM_TITLE"] - заголовок формы
  • $arResult["FORM_DESCRIPTION"] - описание формы
  • $arResult["arForm"]["BUTTON"] - текст кнопки Отправить

Для вывода полей надо брать вопросы из $arResult["QUESTIONS"] по символьному коду.

Структура вопроса:

  • CAPTION - имя поля
  • IS_HTML_CAPTION - имя поля в формате HTML, Y/N.
  • REQUIRED - обязательно к заполнению, Y/N.
  • IS_INPUT_CAPTION_IMAGE - есть ли изображение вопроса
  • HTML_CODE - HTML код поля
  • STRUCTURE - детальное описание поля
  • IMAGE - описание изображения вопроса, если IS_INPUT_CAPTION_IMAGE равен Y.

Например, чтобы вывести инпут поля с символьным кодом NAME надо сделать в шаблоне так:

<?=$arResult["QUESTIONS"]['NAME']['HTML_CODE']?>

На основе переменных выше соберем шаблон

<?
if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();
?>

<? if ($arResult["isFormNote"] === "Y"): ?>
	Спасибо, ваша заявка принята!
<? else: ?>
	<?=$arResult["FORM_HEADER"]?>
	<input type="hidden" name="web_form_submit" value="Y">

	<? if ($arResult["isFormErrors"] === "Y"): ?>
		<div class="errors">
			<?=$arResult["FORM_ERRORS_TEXT"]?>
		</div>
	<? endif; ?>

	<?=$arResult["QUESTIONS"]['NAME']['CAPTION']?>
	<?=($arResult["QUESTIONS"]['NAME']['REQUIRED'] === 'Y' ? ' *' : '')?>:
	<?=$arResult["QUESTIONS"]['NAME']['HTML_CODE']?><br>

	<?=$arResult["QUESTIONS"]['PHONE']['CAPTION']?>
	<?=($arResult["QUESTIONS"]['PHONE']['REQUIRED'] === 'Y' ? ' *' : '')?>:
	<?=$arResult["QUESTIONS"]['PHONE']['HTML_CODE']?><br>

	<?=$arResult["QUESTIONS"]['MESSAGE']['CAPTION']?>
	<?=($arResult["QUESTIONS"]['MESSAGE']['REQUIRED'] === 'Y' ? ' *' : '')?>:
	<?=$arResult["QUESTIONS"]['MESSAGE']['HTML_CODE']?><br>

	<input type="submit" value="<?=$arResult["arForm"]["BUTTON"]?>">

	<?=$arResult["FORM_FOOTER"]?>
<? endif; ?>

Логика его такая:

Вначале проверяем, подключает ли файл Битрикс или его открыл пользователь введя в браузере адрес шаблона. Если не битрикс - блокируем генерацию шаблона.

Если форма успешно отправлена - выводить сообщение, иначе - выводим форму.

<?=$arResult["FORM_HEADER"]?> - выводит открытие формы со служебными скрытыми полями.

Поле web_form_submit в значении Y - триггер для Битрикс, говорящее о том, что форма отправлена и надо бы проверить, что там ввел пользователь.

Если есть ошибки - выводим их в диве с классом errors.

Далее выводим три поля, у каждого: Имя, если обязательное - галочка, HTML код инпута для ввода.

Перед закрытием формы - кнопка отправить с тексом, который указан в настройках формы.

На этом можно закончить, форма уже будет работать.

Ajax отправка

Еще не забыли про ajax? Сейчас покажу вариант, который работает даже без JQuery.

Создайте в папке шаблона компонента файл script.js с контентом:

function ajaxForm(obForm, link) {
    BX.bind(obForm, 'submit', BX.proxy(function(e) {
        BX.PreventDefault(e);
        obForm.getElementsByClassName('error-msg')[0].innerHTML = '';

        let xhr = new XMLHttpRequest();
        xhr.open('POST', link);

        xhr.onload = function() {
            if (xhr.status != 200) {
                alert(`Ошибка ${xhr.status}: ${xhr.statusText}`);
            } else {
                var json = JSON.parse(xhr.responseText)

                if (! json.success) {
                    let errorStr = '';
                    for (let fieldKey in json.errors) {
                        errorStr += json.errors[fieldKey] + '<br>';
                    }
                    
                    // Ошибки вывести в элемент с классом error-msg
                    obForm.getElementsByClassName('error-msg')[0].innerHTML = errorStr;
                } else {
                    // Показываем сообщение об успешной отправке
                    // popupSuccess()
                }
            }
        };

        xhr.onerror = function() {
            alert("Запрос не удался");
        };

        // Передаем все данные из формы
        xhr.send(new FormData(obForm));
    }, obForm, link));
}

Этот скрипт обрабатывает отправку формы, которая передается первым параметром, отправляя все данные из нее на ссылку, переданную вторым параметром. popupSuccess() - это какой-то ваш вызов сообщения об успешной отправке формы.

В шаблон (наш template.php) после <?=$arResult["FORM_HEADER"]?> добавьте строку <div class="error-msg"></div>. В конец файла добавьте следующее:

<script>
    ajaxForm(document.getElementsByName('<?=$arResult['arForm']['SID']?>')[0], '<?=$templateFolder?>/ajax.php')
</script>

Выше мы привязали через функцию ajaxForm() ajax к форме. В $templateFolder хранится ссылка на папку текущего шаблона, в ней мы создадим файл ajax.php. Путь файла, например, может получиться такой: /bitrix/templates/books/components/bitrix/form.result.new/request_form/ajax.php.

Последний штрих - создать файл обработчик ajax запросов.

Создайте в папке шаблона компонента файл ajax.php с контентом:

<?php
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

// Подключаем модуль веб-форм
CModule::IncludeModule("form");

// Проверка валидности отправки формы
if (check_bitrix_sessid()) {
    $formErrors = CForm::Check($_POST['WEB_FORM_ID'], $_REQUEST, false, "Y", 'Y');

    // Если не все обязательные поля заполнены
    if (count($formErrors)) {
        echo json_encode(['success' => false, 'errors' => $formErrors]);
    } elseif ($RESULT_ID = CFormResult::Add($_POST['WEB_FORM_ID'], $_REQUEST)) {

        // Отправляем все события как в компоненте веб форм
        CFormCRM::onResultAdded($_POST['WEB_FORM_ID'], $RESULT_ID);
        CFormResult::SetEvent($RESULT_ID);
        CFormResult::Mail($RESULT_ID);

        // говорим что успешно заявку получили
        echo json_encode(['success' => true, 'errors' => []]);
    } else {
        // Какие-то еще ошибки произошли
        echo json_encode(['success' => false, 'errors' => $GLOBALS["strError"]]);
    }
} else {
    // Предотвратили CSRF атаку
    echo json_encode(['success' => false, 'errors' => ['sessid' => 'Не верная сессия. Попробуйте обновить страницу']]);
}

// Файл ниже подключать обязательно, там закрытие соединения с базой данных
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_after.php';

В этом файле мы принимаем запрос от пользователя, если есть ошибки, выводим их в JSON, если всё успешно, так же через JSON говорим об успешности.

Аналогично компоненту веб формы, запускаем обработчики событий.

В этом варианте нет минусов, как в варианте с битриксовым аякс, потому что битрикс подменяет каждый раз HTML в нашей форме, а в текущем варианте DOM страницы не трогается.

Ajax. Битриксовый вариант.

Чтобы форма отправлялась без перезагрузки страницы, надо добавить в параметры вызова компонента следующее:

"AJAX_MODE" => "Y",
"AJAX_OPTION_SHADOW" => "N",
"AJAX_OPTION_JUMP" => "N",
"AJAX_OPTION_STYLE" => "Y",
"AJAX_OPTION_HISTORY" => "N",

Кстати, с помощью этих параметров добавить AJAX можно в любой компонент. Эти параметры - не особенность вебформ, реализована работа с ajax в базовом классе компонентов.

<?$APPLICATION->IncludeComponent("bitrix:form.result.new", "request_form", Array(
		"CACHE_TIME" => "3600",	// Время кеширования (сек.)
		"CACHE_TYPE" => "A",	// Тип кеширования
		"CHAIN_ITEM_LINK" => "",	// Ссылка на дополнительном пункте в навигационной цепочке
		"CHAIN_ITEM_TEXT" => "",	// Название дополнительного пункта в навигационной цепочке
		"COMPOSITE_FRAME_MODE" => "A",	// Голосование шаблона компонента по умолчанию
		"COMPOSITE_FRAME_TYPE" => "AUTO",	// Содержимое компонента
		"EDIT_URL" => "",	// Страница редактирования результата
		"IGNORE_CUSTOM_TEMPLATE" => "N",	// Игнорировать свой шаблон
		"LIST_URL" => "",	// Страница со списком результатов
		"SEF_MODE" => "N",	// Включить поддержку ЧПУ
		"SUCCESS_URL" => "",	// Страница с сообщением об успешной отправке
		"USE_EXTENDED_ERRORS" => "Y",	// Использовать расширенный вывод сообщений об ошибках
		"VARIABLE_ALIASES" => array(
			"RESULT_ID" => "RESULT_ID",
			"WEB_FORM_ID" => "WEB_FORM_ID",
		),
		"WEB_FORM_ID" => "1",	// ID веб-формы
		"AJAX_MODE" => "Y", 
		"AJAX_OPTION_SHADOW" => "N", 
		"AJAX_OPTION_JUMP" => "N", 
		"AJAX_OPTION_STYLE" => "Y", 
		"AJAX_OPTION_HISTORY" => "N", 
	),
	false
);?>

Теперь Битрикс сам добавит нужные скрипты.

Алгоритм работы Ajax формы:

  1. Весь шаблон формы оборачивается в тег с уникальным ID.
  2. После этого тега добавляется JS, который устанавливает событие на отправку формы.
  3. Пользователь отправляет форму
  4. На сервер запрос через весь шаблон доходит до вызова компонента
  5. Битрикс обрабатывает введенные данные
  6. Получает вывод шаблона формы и заменяет на странице в теге, добавленном в пункте 1, всё содержимое.
  7. Результат: страница не перезагружалась, пользователь видит вместо формы сообщение об успешной отправке или ту же форму с ошибками.

Из этой логики мы получаем некоторые нюансы. Поля формы, обработанные на джаваскрипте при загрузке страницы, снова будут не обработанные, так как они заменились, а события загрузки страницы заново не было.

Особенность 1

Если в форме в поле телефона на JS ставится маска, например $('.my_phone_input').mask('9 (999) 999 9999'), то маска пропадет после замены битриксом формы. Поэтому маску придется накладывать в файле template.php, чтобы она накладывалась каждый раз заново.

Особенность 2

Если мы форму используем в попапе, а при открытии окна верстка копируется в отдельный блок (так работает fancybox) ajax режим работать не будет.

Представим ситуацию: верстку всего попапа мы убрали в шаблон формы. Имеем структуру на странице, как показано ниже.

#1 - блок компонента формы. Эту верстку мы генерируем в шаблоне на сервере.

#2 - блок, который появляется при открытии формы на фронте. В него копируется наша верстка.

<!-- Блок #1 -->
<div id="uniqBitrixFormId">
	<div class="popup" id="myPopup">
		<div class="popup-header">
			Заголовок попапа
		</div>
		<div class="popup-body">
			Наша форма
		</div>
	</div>
</div>

<!-- Блок #2 -->
<div class="opened-popup">
	<div class="popup" id="myPopup">
		<div class="popup-header">
			Заголовок попапа
		</div>
		<div class="popup-body">
			Наша форма
		</div>
	</div>
</div>

Проблема в том, что ajax будет заменять контент скрытого блока, внутри блока #1, а в блоке #2, который видит пользовать, изменений не будет.

Как исправить это? Вынести разметку попапа из шаблона компонента формы, то есть в нашем случае, вызов компонента сделать внутри <div class="popup-body">. Тогда <div id="uniqBitrixFormId"> будет копироваться вместе с версткой формы и у пользователя будет работать ajax.

Настройка капчи

Чтобы в форме появилась капча:

Добавьте в шаблон формы следующий код:

<? if ($arResult["isUseCaptcha"] === true): ?>
	<?=$arResult["CAPTCHA_IMAGE"]?><br>
	Введите код с картинки:
	<?=$arResult["CAPTCHA_FIELD"]?>
<? endif; ?>

В настройках формы включите показ капчи.

На этом капча уже будет работать.

Обновление капчи пользователем.

Бывает такое, что код с картинки не читается и пользователь не может отправить форму. Поможем пользователю.

Вместо кода

<?=$arResult["CAPTCHA_IMAGE"]?>

Сделаем

<img
	src="/bitrix/tools/captcha.php?captcha_sid=<?=$arResult["CAPTCHACode"]?>"
	onclick="this.src = '/bitrix/tools/captcha.php?captcha_sid=<?=$arResult["CAPTCHACode"]?>&r='+Math.random()"
	style="cursor:pointer"
	width="180"
	height="40"
>
Нажмите на картинку, чтобы обновить

Теперь, если пользователь не может прочитать код, он может обновить картинку.

Настройка формата изображения капчи

Вид капчи можно настроить в панели администратора в разделе Настройки > Настройки продукта > CAPTCHA.

Своя верстка полей ввода в форме

До этого момента поля в форму мы вставляли HTML_CODE вопроса, в котором уже содержится код инпута. Давайте и его сами генерировать, ведь бывает и такая необходимость.

Функцию генерации поля ввода объявим как анонимную, чтобы можно было копировать шаблон компонента и не было ошибок на странице, что одна функция объявлена 2 раза.

Наши требования:

  1. Для поля должен быть указан соответствующий тип type.
  2. Требуется указать имя поля (name).
  3. Если форма отправлена и были ошибки, значения в поле не должны пропадать.
  4. Если поле обязательно к заполнению, должен быть атрибут required.
  5. Для полей ввода номера телефона добавим класс phone.
  6. Должна быть поддержка двух типов полей: text и textarea, так как в форме мы использовали только их.

В файле result_modifier.php шаблона формы добавим такой код:

<? if(!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die();

$arResult['funcGetInputHtml'] = function($question, $arrVALUES) {
	$id = $question['STRUCTURE'][0]['ID'];
	$type = $question['STRUCTURE'][0]['FIELD_TYPE'];
	$name = "form_{$type}_{$id}";
	$value = isset($arrVALUES[$name]) ? htmlentities($arrVALUES[$name]) : '';
	$required = $question['REQUIRED'] === 'Y' ? 'required' : '';
	$class = ' ' . $question['STRUCTURE'][0]['FIELD_PARAM'];
	
	switch ($type)
	{
		case 'textarea':
			$input = "<textarea class=\"form-message {$class}\" name=\"{$name}\" {$required}>{$value}</textarea>";
			break;
		
		// case 'text':
		default:
			$input = "<input class=\"call__form-input {$class}\" type=\"text\" name=\"{$name}\" value=\"{$value}\" {$required}>";
			break;
	}
	
	return $input;
};

Мы создали функцию, которая попадет в файл template.php, установили имя, класс из параметров вопроса, атрибут обязательного поля, значение, введенное пользователем.

В template.php вместо вывода готового HTML кода поля, выведем его через нашу функцию.

<?=$arResult["QUESTIONS"]['NAME']['HTML_CODE']?>
заменим на
<?=$arResult['funcGetInputHtml']($arResult["QUESTIONS"]['NAME'], $arResult['arrVALUES'])?>
Вместо стандартного
<input type="text" name="form_text_1" value="" size="0">
получим
<input class="call__form-input  " type="text" name="form_text_1" value="" required="">
Пожалуйста, оцените на сколько вам понравилась статья!
Голосов: 6 Среднее: 5