• Блог
  • Ajax форма обратной связи

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

На многих сайтах используется форма обратной связи для общения с клиентом. Давайте возьмём обычную форму и сделаем её аяксовой.

Для этого нам понадобится дополнение FormIt, AjaxForm и reCaptchaV2 (каптча от Google). Если они у вас еще не установлены, то пора установить и настроить каптчу. Кроме того, я использую стили фреймворка Bootstrap в разметке формы.

Мы будем делать вот такую форму.

Когда пользователь заполнит все поля и нажмёт «Отправить», форма скроется, а вместо неё появится сообщение, что форма успешно отправлена. Тут вариантов может быть несколько — выводить модальное окно или ничего не выводить, так как AjaxForm покажет короткое сообщение об успешной отправке через jGrowl. В данном примере мы будем скрывать форму и показывать блок с сообщением об успешной отправке.

Разместим форму на странице контактов. Вставляем следующий код в нужном месте разметки.

[[!AjaxForm?
    &snippet=`FormIt`
    &form=`tpl.AjaxForm.contactForm`
    &hooks=`recaptchav2,email`
    &emailTpl=`tpl.FormIt.email`
    &emailSubject=`Сообщение от пользователя с сайта Сайт.ру`
    &emailTo=`info@site.ru`
    &validate=`name:required:minLength=^3^,
               email:email:required,
               message:required:stripTags,
               g-recaptcha-response:required`
]]
<!-- Этот блок будет выводится когда пользователь отправит сообщение -->
<div id="success-response" class="alert alert-info" style="display: none;">
    <p>Сообщение отправлено.</p>
    <p>Спасибо за обращение. <a href="#" id="onemore-feedback">Отправить</a> еще сообщение.</p>
</div>

Тут мы видим, что вызывается сниппет FormIt, шаблон формы находится в чанке tpl.AjaxForm.contactForm, используются хуки recaptchav2 (каптча) и email (отправка email), указаны тема сообщения и адрес получателя (тут нужно указать свои), а также условия проверки полей — они все обязательные.

Чанк tpl.AjaxForm.contactForm выглядит так

<div id="contactform-wrap">
    <form id="contactform" method="post" action="">
        <div class="row">
            <div class="col-sm-6">
                <input class="form-control" type="text" name="name" placeholder="Имя">
            </div>
            <div class="col-sm-6">
                <input class="form-control" type="text" name="email" placeholder="Email">
            </div>
        </div>
        <div class="row">
            <div class="col-sm-12">
                <textarea placeholder="Введите ваше сообщение..." class="form-control" rows="4" name="message">
            </div>
        </div>
        <div class="row">
            <div class="col-xs-6">
                [[!recaptchav2_render]]
            </div>
            <div class="col-xs-6 text-right">
                <input class="btn my-btn btn-primary btn-lg" type="submit" value="Отправить">
            </div>
        </div>
    </form>
</div>

Но это еще не всё. AjaxForm умеет выводить сообщения об ошибках заполнения формы. Но давайте сделаем их более детальными. Для этого нам нужно добавить на сайт скрипт, который будет обрабатывать информацию от AjaxForm.

$(document).on('af_complete', function(event, response) {
    var fields = {
        name: "Имя",
        email: "Email",
        message: "Сообщение",
        "g-recaptcha-response": "Каптча"
    };
    if (response.success) {
        // Скрываем форму и показываем блок!
        response.form.hide();
        if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
        $("#success-response").fadeIn(700);
    } else {
        for (var prop in response.data) {
            // Выводим сообщение через jGrowl для всех полей с ошибками.
            // Если хотите, чтобы сообщения оставались, добавьте вторым параметром true
            AjaxForm.Message.error("Заполните поле '"+fields[prop]+"'");
        }
    }
    // Отменяем вывод стандартного сообщения
    response.message='';
});
// Вешаем обработчик
$(document).on('click', '#onemore-feedback', function(e) {
    $("#success-response").fadeOut();
    $("#contactform").fadeIn();
    e.preventDefault();
});

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

Вот теперь всё. Если будут вопросы, задавайте. Еще почитать про FormIt можно тут.

П.С. Обязательно прочтите статью Безопасный FormIt.

0   37923

Комментарии ()

  1. Валерий 24 августа 2016 # 0
    Большое спасибо за запись. Именно то, что давно искал. За JS все никак не могу полноценно взяться (.

    У меня такой вопрос. А если форм на странице больше одной? То нужно для каждой отдельный скрипт обработки написать, заменив название полей и классы\идентификаторы? Объединить код не получится?
    1. Сергей Шлоков 24 августа 2016 # 0
      Дописывать конечно нужно, но можно в одном скрипте. Если обратите внимание на вторую строчку скрипта
      $(document).on('af_complete', function(event, response) {
          var form = response.form;
      то увидите, что AjaxForm понимает с какой формой работает. Для каждой формы нужно прописать свой обработчик.
      1. arkadyexp 22 января 2017 # 0
        Простите, ужасно туплю по поводу двух форм на одной странице, а конкретно насчет var form. Что нужно конкретно прописать чтобы оно понимало с какой формой работает?:(
        1. Сергей Шлоков 22 января 2017 # 0
          Возможно этот комментарий поможет.
          1. arkadyexp 22 января 2017 # 0
            Уфф… спасибо огромное, я правда в муках и с трудом но наконец то родил по инструкции из того примера) о чудо, оно работает!:v в следующий раз буду читать комменты до конца))) надеюсь для 5 форм тоже прокатит.
            1. arkadyexp 23 января 2017 # 0
              Кстати в мобильной версии была проблема с тем что после отправки формы не было видно окна уведомления, решил добавлением строчки document.location.href, которая перетаскивает по сайту к указанному id, чтобы у того кто отправил форму не было ступора) причем id с именем xxx должен располагаться выше и вне скрываемой формы, иначе не работает пачимуто. Т.е. вариант откатить к contactform не прокатывает.

              if (form.attr('id') == 'contactform') {
              		form.hide();
              		if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
              		$("#success-response").fadeIn(700);
              		document.location.href = '#xxx';
              	    response.message='';
              	}
      2. Артем 26 августа 2016 # +1
        Добрый день!
        Спасибо Вам за интересное и полезное решение.
        У меня есть один вопрос: при установке на сайт возникает казусная ситуация, кстати у Вас на сайте тоже — делаем ошибку в форме и обработчик возвращает сообщение об ошибочных полях и сообщение ошибки от AjaxForm, причем на английском языке. Вы не думали как устранить данную тавтологию?
        1. Сергей Шлоков 26 августа 2016 # 0
          Здравствуйте!
          Решается это легко.
          response.message='';
          Обновил скрипт. Спасибо за внимательность.
        2. Артем 28 августа 2016 # 0
          Я бы еще добавил эту функцию response.message=' ' в оператор if (response.success), чтобы зеленого сообщения, что форма отправлено не появлялось, т.к. у нас об этом сообщает блок с id=«success-response». Что скажете?
          1. arkadyexp 23 сентября 2016 # 0
            Форма просто великолепная, но я так и не докурил как приложить файл к сообщению в этой форме. Подскажите пожалуйста, а то я в php полный даун) Этоже в скрипте где то решается?
            1. arkadyexp 23 сентября 2016 # 0
              У
              1. arkadyexp 23 сентября 2016 # 0
                Вроде получилось, так работает

                <div id="contactform-wrap">
                <form id="contactform" method="post" enctype="multipart/form-data">
                .................
                <div class="row">
                    <div class="col-xs-12">
                        <p>Вы можете приложить к письмо до 3-х файлов</p>
                        <input type="file" name="file" />
                        <input type="file" name="file2" />
                        <input type="file" name="file3" />
                                    
                 .................
              2. arkadyexp 25 сентября 2016 # 0
                … сегодня мой квест получил продолжение, выяснилось что рекапча2 практически не пашет в мозиле и эксплоире, точней выводится долго, со второго клика и в очень корявом виде, как будто не работает адобовский плеер или джава. В хроме и опере работает отлично. Устанавливал все по вашему уроку. Хэлп ми:s. Забыл добавить, что сайт пока находится на кирилическом поддомене.
                1. Сергей Шлоков 25 сентября 2016 # +1
                  К сожалению, у меня нет предположений в чём причина такого поведения. Лично у меня всё работает отлично и в мозиле и в ie11.
                2. kalisto 27 сентября 2016 # 0
                  Прошу вашей помощи.
                  &hooks=`spam,email,FormItSaveForm`
                  ранее в админке skrinshoter.ru/s/270916/LELnG1 могла видеть все заполненные формы, а теперь только на почту получаю. Как исправить?
                  1. Сергей Шлоков 27 сентября 2016 # +1
                    Сложно сказать. Я с такой проблемой не сталкивался. Попробуйте обратиться в сообщество modx.pro. Всё-таки там много программистов, должны помочь.
                    1. kalisto 27 сентября 2016 # 0
                      Сергей! Это связано с Вашими компонентами AdminTools и controlErrorLog. Может что-то в паре действует?
                      Я сейчас пробую то один поставить, то другой и смотреть какое дополнение это вызывает.
                      Я сегодня поставила 2 Ваших дополнения controlErrorLog и AdminTools
                      Я если их деинсталлирую, то в админке отображаются отправленные формы — skrinshoter.ru/s/270916/QgGUXS
                      Заметила такую особенность — форму отправляю, AdminTools установлен — данные в админке появляются. Удаляю одну форму, данные исчезают.
                      Как исправить?
                      1. kalisto 27 сентября 2016 # 0
                        В консоле пишет в админке olgashalashova.ru/:1 Uncaught SyntaxError: Unexpected end of JSON input
                        После удаления одной записи идет загружается, а потом все исчезаетЗагружается…
                        1. Сергей Шлоков 27 сентября 2016 # +1
                          У меня на сайте установлены все мои дополнения и формы отображаются без проблем. Допускаю, что возможен конфликт скриптов с каким-нибудь дополнением. Но это можно выяснить только вживую.
                          1. kalisto 27 сентября 2016 # 0
                            Вы сможете посмотреть или нет? интересно ведь узнать причину
                            Если да, то доступ кудаи какой Вам отправить?
                            1. Сергей Шлоков 27 сентября 2016 # +1
                              Смогу глянуть поздно вечером. Выслать можно в контактах в форме обратной связи.
                              1. kalisto 27 сентября 2016 # 0
                                Отправила.
                    2. Евгений 01 ноября 2016 # 0
                      Не подскажете, как изменить скрипт, чтоб проверка была без рекапчи, а просто на успешную отправку формы?
                      1. Сергей Шлоков 01 ноября 2016 # 0
                        Уберите из вызова AjaxForm в параметре validate строчку g-recaptcha-response: required. Ну и в чанке tpl.AjaxForm.contactForm уберите вызов сниппета
                        <div class="col-xs-6">
                            [[!recaptchav2_render]]
                        </div>
                        
                        1. Евгений 01 ноября 2016 # 0
                          Форма скрывается, после отправки. Но не появляется блок
                          <div id="success-response" class="alert alert-info" style="display: none;">
                              <p>Сообщение отправлено.</p>
                              <p>Спасибо за обращение. <a href="#" id="onemore-feedback">Отправить</a> еще сообщение.</p>
                          </div>
                          В кослоли ругается на эту строчку
                          if (typeof $('.recaptcha') != "undefined") grecaptcha.reset();
                          1. Сергей Шлоков 01 ноября 2016 # +1
                            Замените эту строчку на
                            if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
                            
                            1. Евгений 01 ноября 2016 # 0
                              Спасибо! Работает!
                              1. Евгений 01 ноября 2016 # 0
                                Еще вопрос.

                                На странице у меня две формы. Соответственно, в чанке второй формы поменял id и добавил еще один скрипт

                                <script>
                                $(document).on('af_complete', function(event, response) {
                                    var fields = {
                                        name: "Имя",
                                        email: "Email",
                                        message: "Сообщение",
                                        "g-recaptcha-response": "Каптча"
                                    };
                                    if (response.success) {
                                        response.form.hide();
                                        if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
                                        $("#success-response2").fadeIn(700);
                                    } else {
                                        for (var prop in response.data) {
                                            AjaxForm.Message.error("Заполните поле '"+fields[prop]+"'");
                                            response.message='';
                                        }
                                    }
                                });
                                $(document).on('click', '#onemore-feedback2', function(e) {
                                    $("#success-response2").fadeOut();
                                    $("#contactformB").fadeIn();
                                    e.preventDefault();
                                });
                                </script>
                                Формы работаю, но когда я отправляю форму, которая ниже, то в первой форме тоже появляется уведомление «Сообщение отправлено!»

                                Что я делаю не так?
                                1. Сергей Шлоков 01 ноября 2016 # 0
                                  Не надо добавлять 2 события af_complete. Достаточно одного, а в нем прописать логику для каждой формы —
                                  ...
                                  if (response.success) {
                                  	var form = response.form;
                                  	if (form.attr('id') == 'my_form_1') {
                                  		form.hide();
                                  		//...
                                  	} else if (form.attr('id') == 'my_form_2') {
                                  		form.hide();
                                  		//...
                                  	}
                                  } else {
                                  	...
                                  }
                                  
                                  Вот документация по AjaxForm.
                        2. Максим 15 марта 2017 # 0
                          Доброго времени суток. Помогите разобраться с двумя формами на странице. Где нужно сменить id? самой формы?
                          1. Сергей Шлоков 15 марта 2017 # 0
                            Ага.
                            1. Максим 16 марта 2017 # 0
                              на данный момент две формы form id=«form1» и form id=«form2» в каждой из форм вызываю [[!recaptchav2_render]] но в первой форме есть капча, а во второй не появляется. Не понимаю как запустить капчу во второй форме.
                              1. Сергей Шлоков 16 марта 2017 # 0
                                Компонент reCaptcha2 не предназначен для работы с несколькими формами. Его нужно или дорабатывать или пилить свой вариант по доке.
                            2. Эрадж 13 апреля 2020 # 0
                              На правильность кода не претендую, но данный отрабатывает для 4-х форм на ура
                              $(document).on('af_complete', function(event, response) 
                                  {
                                      var fields = {
                                          name: "Имя",
                                          phone: "Номер телефона",
                                          "g-recaptcha-response": "Каптча"
                                      };
                                      
                                      var form = response.form;
                                          // Если у формы определённый id
                                          if (form.attr('id') == 'order-menu') 
                                              {
                                              // Скрываем её!
                                              form.hide();
                                              if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
                                              $("#order-menu-success").fadeIn(700);
                                              }
                                              
                                          if (form.attr('id') == 'order-landing') 
                                              {
                                              // Скрываем её!
                                              form.hide();
                                              if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
                                              $("#order-landing-success").fadeIn(700);
                                              }
                                          if (form.attr('id') == 'order-opros') 
                                              {
                                              // Скрываем её!
                                              form.hide();
                                              if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
                                              $("#order-opros-success").fadeIn(700);
                                              }
                                          if (form.attr('id') == 'order-tovar') 
                                              {
                                              // Скрываем её!
                                              form.hide();
                                              if (document.getElementsByClassName('g-recaptcha').length) grecaptcha.reset();
                                              $("#order-tovar-success").fadeIn(700);
                                              }
                                  
                                      else 
                                           {
                                              for (var prop in response.data) {
                                              // Выводим сообщение через jGrowl для всех полей с ошибками.
                                              // Если хотите, чтобы сообщения оставались, добавьте вторым параметром true
                                              AjaxForm.Message.error("Заполните поле '"+fields[prop]+"'");
                                      }
                                  }
                                  
                                  // Отменяем вывод стандартного сообщения
                                  response.message='';
                              });
                              // Вешаем обработчик
                              $(document).on('click', '#onemore-feedback', function(e) {
                                  $("#order-menu-success,#order-landing-success,#order-opros-success,#order-tovar-success").fadeOut();
                                  $("#order-menu,#order-landing,#order-opros,#order-tovar").fadeIn();
                                  e.preventDefault();
                              });
                            3. Дмитрий 14 июля 2017 # 0
                              Здравствуйте!
                              Спасибо большое!
                              Почти совсем — «то, что доктор прописал» :-)
                              До сих пор не использовал AjaxForm, т.к. очень не нравится та js библиотека, которая отрабатывает вывод сообщений — т.е. вот эти всплывашки в правом верхнем углу.
                              В очередной раз стал искать самоделки на Formit + Ajax, которые бы выводили message без перезагрузки прямо над формой и наткнулся на вашу статью.
                              Сообщение об отправке — просто чудесно.
                              А можно ли сделать так, чтобы и сообщения об ошибках перехватывались вашим скриптом и выводились над полями формы в блоке с id=«success-response»?
                              1. Сергей Шлоков 14 июля 2017 # 0
                                Добрый вечер!
                                Конечно. Для этого достаточно немного знать jQuery. Можно выводить их в блоке списком ul.
                              2. Андрей 15 декабря 2017 # 0
                                Сергей, подскажите. Использую AjaxForm со своим сниппетом. Можно ли вместе с успешным ответом
                                return $AjaxForm->success('Спасибо, все люкс');
                                вернуть какие-=то данные на страницу?
                                В частности, мне нужно выполнить сниппет в определенном div
                                1. Сергей Шлоков 15 декабря 2017 # 0
                                  Можно. Вторым параметром передавайте нужные данные, а в яваскрипте ловите событие «af_complete» и обрабатывайте полученные данные.
                                  1. Андрей 15 декабря 2017 # 0
                                    Типа такого нужно сделать?
                                    return $AjaxForm->success('Спасибо, все люкс', array(
                                            'name' => $name,
                                            'param' => $param,
                                        ));
                                    1. Сергей Шлоков 15 декабря 2017 # 0
                                      Да.
                                      1. Андрей 15 декабря 2017 # 0
                                        Ага, спасибо!
                                        1. Андрей 15 декабря 2017 # 0
                                          Сергей, спасите))
                                          Никак у меня не передается ничего((
                                          Вот мой сниппет:
                                          if (!empty($_POST['name'])) { 
                                              $res = '';
                                              $params = array();
                                              $params['tpl'] = 'tpl';
                                              $res = $modx->runSnippet('mod', $params);
                                              return $AjaxForm->success('Спасибо, сейчас вы увидите билеты', $res);
                                          }
                                          То есть смысл в том, что я хочу при успешном ответе в определенный блок вывести результаты работы сниппета mod
                                          Вот JS:
                                          $(document).on('af_complete', function(event, response) {
                                              var form = response.form;
                                              if (form.attr('id') == 'reg_form') {
                                                  form.hide();
                                                  if (response.res !== undefined) $('.outer').val(response.res);
                                              }
                                          });
                                          В итоге всегда response.res = undefined
                                          1. Сергей Шлоков 15 декабря 2017 # 0
                                            Во-первых, сниппет всегда выводит строку. Во-вторых, в яваскрипте выполните
                                            console.log(response);
                                            и вы увидите всё что возвращается с сервера. Как минимум увидите, что нет response.res, а есть response.data.
                                            1. Андрей 15 декабря 2017 # 0
                                              Согласен. Заменил везде res на data
                                              if (!empty($_POST['name'])) { 
                                                  $data = '';
                                                  $params = array();
                                                  $params['tpl'] = 'tpl';
                                                  $data = $modx->runSnippet('mod', $params);
                                                  return $AjaxForm->success('Спасибо, сейчас вы увидите билеты', $data);
                                              }
                                              JS (немного сократил для поста):
                                              $(document).on('af_complete', function(event, response) {
                                                  var form = response.form;
                                                      console.log(response);
                                                  }
                                              })
                                              ответ такой
                                              {success: true, message: «Спасибо, сейчас вы увидите билеты», data: Array(0), form: m.fn.init(1)}
                                              1. Сергей Шлоков 15 декабря 2017 # 0
                                                Сниппет что возвращает? В каком формате?
                                                1. Андрей 15 декабря 2017 # 0
                                                  А вот я вам приводил его выше полностью практически.
                                                  if (!empty($_POST['name'])) { 
                                                      $data = '';
                                                      $params = array();
                                                      $params['tpl'] = 'tpl';
                                                      $data = $modx->runSnippet('mod', $params);
                                                      return $AjaxForm->success('Спасибо, сейчас вы увидите билеты', $data);
                                                  }
                                                  Ниже кода нет, return этого не достаточно? Подскажите, как правильно написать?
                                                  1. Сергей Шлоков 15 декабря 2017 # 0
                                                    Я про сниппет mod имел ввиду.
                                                    1. Андрей 15 декабря 2017 # 0
                                                      Вот его код полностью
                                                      <?php
                                                      /** @var modX $modx */
                                                      /** @var array $scriptProperties */
                                                      /** @var modTest $modTest */
                                                      $modTest = $modx->getService('modTest', 'modTest', MODX_CORE_PATH . 'components/modtest/model/', $scriptProperties);
                                                      if (!$modTest) {
                                                          return 'Could not load modTest class!';
                                                      }
                                                      $tpl = $modx->getOption('tpl', $scriptProperties);
                                                      // Build query
                                                      $q = array(
                                                          'active' => 1,
                                                      );
                                                      $pdo = $modx->getService('pdoFetch');
                                                      $rows = $pdo->getCollection('modTestTicket', $q);
                                                      $output = '';
                                                      foreach ($rows as $row) {
                                                      	$output .= $pdo->getChunk($tpl, $row);
                                                      }
                                                      return $output;
                                                      1. Сергей Шлоков 15 декабря 2017 # 0
                                                        Данные надо ловить в response.data. Что в
                                                        console.log(response.data)
                                                        1. Андрей 15 декабря 2017 # 0
                                                          Ответ пустой, к сожалению((
                                                          []
                                                          1. Андрей 16 декабря 2017 # 0
                                                            Сергей, простите за беспокойство. Все из-за моей невнимательности) При очередной сборке компонента сниппет перестал быть статическим, а все правки в файле сниппета по сути не влияли ни на что) Вот такая вот беда. Видимо, вечер пятницы дает о себе знать…
                                                            Сейчас все передается отлично. Спасибо!
                                    2. Кирилл 19 августа 2019 # 0
                                      А как изменить этот момент? (Выводим сообщение через jGrowl для всех полей с ошибками)
                                      else {
                                              for (var prop in response.data) {
                                                  // Выводим сообщение через jGrowl для всех полей с ошибками.
                                                  // Если хотите, чтобы сообщения оставались, добавьте вторым параметром true
                                                  AjaxForm.Message.error("Заполните поле '"+fields[prop]+"'");
                                              }
                                      Добавил себе скрытое поле от спама и хотелось бы, чтобы сообщение об ошибке его не палило, т.е. как исключить это поле из всплывающих сообщений об ошибках?
                                      1. Andrei 29 июля 2020 # 0
                                        Добрый день, сделал все по инструкции, но почему то текст чанка tpl.AjaxForm.contactForm выводится в поле ввода сообщения.
                                        1. Andrei 29 июля 2020 # 0
                                          Всё, устранил ошибку)
                                        2. Сергей 01 сентября 2020 # 0
                                          Не работает в браузерах Сафари и Самсунг. Друзья, кто знает как это исправить?
                                          1. Сергей 27 ноября 2020 # +1
                                            Отвечаю сам себе. В Сафари был отключен pop-up, в Самсунге проверить не мог, вероятно тоже что-то было изменено в настройках браузера.

                                          Вы должны авторизоваться, чтобы оставлять комментарии.

                                          Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.