Отправка данных из формы bitrix через кастомный ajax запрос

Оглавление

Используя модуль bitrix webforms создаём новую форму.

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

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, "N", 'Y');

  // Если не все обязательные поля заполнены
  if (count($formErrors)) {
    echo json_encode(['success' => false, 'errors' => $formErrors, 'data' => []]);
  } 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' => [], 'data' => ['result_id' => $RESULT_ID]]);
  } else {
    // Какие-то еще ошибки произошли
    echo json_encode(['success' => false, 'errors' => $GLOBALS["strError"], 'data' => []]);
  }
} else {
  // Предотвратили CSRF атаку
  echo json_encode(['success' => false, 'errors' => ['sessid' => 'Не верная сессия. Попробуйте обновить страницу'], 'data' => []]);
}

// Файл ниже подключать обязательно, там закрытие соединения с базой данных
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/epilog_after.php';
ajax.js
/**
 * Класс BAjax предназначен для отправки формы через AJAX запрос и обработки ответа в виде JSON.
 */
class BAjax {
  static events = {
    before: 'bajax:before',
    success: 'bajax:success',
    error: 'bajax:error',
    after: 'bajax:after',
    reset: 'bajax:reset',
  }
  /**
   * Создает экземпляр класса BAjax.
   * @param {Element} formElement - DOM элемент формы.
   * @param {string} actionUrl - URL адрес, на который будет отправлен запрос.
   */
  constructor(formElement, actionUrl) {
    if (!(formElement instanceof HTMLFormElement)) {
      throw new Error('Не форма');
    }

    this.form = formElement;
    this.actionUrl = actionUrl || this.form.getAttribute('action');
    this.form.addEventListener('submit', this.handleSubmit.bind(this));
    this.form.addEventListener('reset', this.handleReset.bind(this));
  }

  /**
   * Обработчик события отправки формы.
   * @param {Event} event - Событие отправки формы.
   */
  handleSubmit(event) {
    event.preventDefault();

    this.formData = new FormData(this.form);

    this.clearErrors();
    this.toggleDisabledForm(true);

    const beforeEvent = new CustomEvent(BAjax.events.before, {
      cancelable: true,
      detail: {
        core: this,
        form: this.form,
        formData: this.formData,
      },
    });

    if (!document.dispatchEvent(beforeEvent)) {
      this.finally();
      return;
    }

    this.sendAjaxRequest(this.formData);
  }

  /**
   * Отправляет AJAX запрос на сервер с данными формы.
   * @param {FormData} formData - Данные формы.
   */
  async sendAjaxRequest(formData) {
    try {
      const response = await fetch(this.actionUrl, { method: 'POST', body: formData });

      if (!response.ok) {
        this.showErrors({ error: response.statusText });
        console.error(`Ошибка ${response.status}: ${response.statusText}`);
        return;
      }

      const jsonData = await response.json();

      if (!jsonData.success) {
        this.showErrors(jsonData.errors);
        return;
      }

      const successEvent = new CustomEvent(BAjax.events.success, {
        detail: {
          form: this.form,
          formData: this.formData,
          response: jsonData
        },
      });

      if (!document.dispatchEvent(successEvent)) {
        return;
      }
    } catch (error) {
      this.showErrors({ error: error.message });
    } finally {
      this.finally();
    }
  }

  /**
   * Обработчик события сброса формы.
   */
  handleReset() {
    const resetEvent = new CustomEvent(BAjax.events.reset, {
      detail: {
        core: this,
        form: this.form,
      },
    });

    this.toggleDisabledForm(false);
    document.dispatchEvent(resetEvent);
  }

  /**
   * Вызывается после получения ответа на AJAX запрос.
   */
  finally() {
    const afterEvent = new CustomEvent(BAjax.events.after, {
      detail: {
        core: this,
        form: this.form,
      },
    });

    if (!document.dispatchEvent(afterEvent)) {
      return;
    }

    this.handleReset();
  }

  /**
   * Очистка ошибок с формы
   */
  clearErrors() {
    const listDataError = this.form.querySelectorAll('[data-error]');

    listDataError.forEach((i) => (i.textContent = ''));
  }

  /**
   * Отображает ошибки в форме.
   * @param {Object} objErrors - Объект с ошибками.
   */
  showErrors(objErrors = {}) {
    this.clearErrors();

    const errorEvent = new CustomEvent(BAjax.events.error, {
      cancelable: true,
      detail: {
        core: this,
        form: this.form,
        formData: this.formData,
        errors: objErrors,
      },
    });

    if (!document.dispatchEvent(errorEvent)) {
      return;
    }

    const $errorMsg = this.form.querySelector('.error-msg');

    let errorStr = '';
    for (let fieldKey in objErrors) {
      const $dataError = this.form.querySelector(
        `[data-error="${fieldKey}"]`
      );

      if ($dataError) {
        $dataError.textContent = objErrors[fieldKey];
      } else {
        errorStr += objErrors[fieldKey] + '<br>';
      }
    }

    if ($errorMsg) {
      $errorMsg.innerHTML = errorStr;
    } else {
      console.error(objErrors);
    }
  }

  /**
   * Включает или отключает атрибут disabled для элементов формы.
   * @param {boolean} toggle - Флаг для включения или отключения атрибута disabled.
   */
  toggleDisabledForm(toggle) {
    const elements = this.form.querySelectorAll(
      ':scope :not([data-bajax="no-disabled"])'
    );

    elements.forEach((element) => {
      if (toggle) {
        element.setAttribute('disabled', 'disabled');
      } else {
        element.removeAttribute('disabled');
      }
    });
  }
}

В шаблон template.php добавьте тег для вывода ошибок (обычно добавляется после кнопки отправки формы):

<div class="error-msg"></div>

В конец файла добавьте следующее:

template.php
<!-- ... -->
<script src="<?=$templateFolder?>/ajax.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
  const formSender = new BAjax(document.getElementsByName('<?=$arResult['arForm']['SID']?>')[0], '<?=$templateFolder?>/ajax.php');

  // Установка коллбэков (опционально)
  document.addEventListener('bajax:before', (e) => {
    const { form, formData } = e.detail;

    console.log('Before send 🚀', form, formData);
    // e.preventDefault(); // Отменяем отправку формы
  });

  document.addEventListener('bajax:after', () => {
    console.log('After send 🎉');
  });

  document.addEventListener('bajax:success', (e) => {
    const { response } = e.detail;
    console.log('Success 🙌', response);
  });

  document.addEventListener('bajax:error', (e) => {
    const { errors } = e.detail;
    console.log('Error 😢', errors);

    // errors - Объект с ошибками, ключ - название поля, значение - текст ошибки
  });

  document.addEventListener('bajax:reset', () => {
    console.log('Form reset ❌');
  });
});
</script>

Первая строка подключает ajax.js файл с классом для отправки и обработки данных с формы. Далее добавляется код для вызова обработчика ajax и добавление callback событий.

Внутри шаблона можно добавить вывод ошибок рядом с полями в которых была ошибка. Для этого добавляем тег с атрибутом data-error, значением которого является название поля:

<div data-error="name"></div>

При отправки всем элементам формы добавляется атрибут disabled, если каким либо тегам не надо добавлять, то надо добавить в этот тег атрибут data-bajax="no-disabled".


Ссылки