OpenCart. Плавающая форма обратной связи через AJAX.
JavaScript, PHP, Разное

Небольшой, но подробный туториал для начинающих разработчиков.

Возникла потребность в плавающей, выдвигающейся сбоку форме обратной связи (обратный звонок). К сожалению, готового решения я так и не нашел, кроме громоздкого, через модуль vqmod. Его суть заключается в перезаписи файлов OpenCart на лету, этакий своеобразный прокси-компонент, или декоратор, если угодно. Идея, конечно, интересная, но не об этом сейчас.

Готовый модуль можно скачать по ссылке в следующей статье «OpenCart. Создаем расширение для установки через админку.»

Мы займемся решением задачи с нуля, рассмотрим в деталях, как это делается. Надеюсь, материал данной статьи поможет вам лучше ориентироваться в OpenCart.

Цель

В развернутом виде, наша форма будет выглядеть так

Отправлять данные будем с помощью AJAX, то есть без перезагрузки страницы. Потребуется написать под это дело контроллер, исправить темплейт header текущей (дефолтной в нашем случае) темы, добавить стили и JavaScript.

Полный список редактируемых файлов:

  • /catalog/view/theme/default/template/common/header.tpl
  • /catalog/view/theme/default/stylesheet/stylesheet.css
  • /catalog/view/javascript/common.js
  • /catalog/controller/ajax/feedback.php – создаем папку и файл

Сперва, создадим код нашей формы:

<!-- contact form start -->
<div class="floating-form" id="contact_form">
<div class="contact-opener">
    <span class="glyphicon glyphicon-phone-alt"></span>
    обратный звонок</div>
    <div class="floating-form-heading">Заполните форму</div>
    <div id="contact_results"></div>
    <div id="contact_body">
        <label><span>Имя <span class="required">*</span></span>
        	<input type="text" name="name" id="name" required="required" class="input-field">
        </label>
        <label><span>Телефон <span class="required">*</span></span>
        	<input type="text" name="phone2" maxlength="20" required="required" class="input-field">
        </label>
        <label><span>Текст </span>
        	<textarea name="message" id="message" class="textarea-field" required="required"></textarea>
        </label>
        <label>
        	<span> </span><input type="submit" id="submit_btn" value="отправить">
        </label>
    </div>
</div>
<!-- contact form end -->

Затем, вставим в header.tpl, сразу после открывающего тэга body. Полный путь здесь и далее смотри выше, в списке файлов.

Теперь добавим стили в самый конец stylesheet.css

Большая CSS простыня (кликни)

/*Floating form css begin*/
.floating-form {
    max-width: 310px;
    padding: 30px 30px 10px 30px;
    font: 13px Arial, Helvetica, sans-serif;
    /*background: #F9F9F9;*/
    background: #FFF9B4;
    border: 1px solid #ddd;
    right: 0px;
    position: fixed;
    box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06);
    -moz-box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06);
    -webkit-box-shadow:  -2px -0px 8px rgba(43, 33, 33, 0.06);
    z-index: 1000;
    -webkit-box-shadow: -5px 5px 17px -4px rgba(0,0,0,0.75);
    -moz-box-shadow: -5px 5px 17px -4px rgba(0,0,0,0.75);
    box-shadow: -5px 5px 17px -4px rgba(0,0,0,0.75);
    border-radius: 10px;
    opacity: 0.98;
    right: -290px;
}

.contact-opener {
    position: absolute;
    left: -110px;
    transform: rotate(-90deg);
    top: 100px;
    background-color: #216288;
    padding: 9px;
    color: #fff;
	text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.43);
    cursor: pointer;
    border-radius: 5px 5px 0px 0px;
    -webkit-border-radius: 5px 5px 0px 0px;
    -moz-border-radius: 5px 5px 0px 0px;
    box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06);
    -moz-box-shadow: -2px -0px 8px rgba(43, 33, 33, 0.06);
    -webkit-box-shadow:  -2px -0px 8px rgba(43, 33, 33, 0.06);
    font-size: 16px !important;

}
.floating-form-heading{
    font-weight: bold;
    font-style: italic;
    border-bottom: 2px solid #ddd;
    margin-bottom: 10px;
    font-size: 15px;
    padding-bottom: 3px;
}
.floating-form label{
    display: block;
    margin: 0px 0px 15px 0px;
}
.floating-form label > span{
    width: 70px;
    font-weight: bold;
    float: left;
    padding-top: 8px;
    padding-right: 5px;
}
.floating-form span.required{
    color:red;
}
.floating-form .tel-number-field{
    width: 40px;
    text-align: center;
}
.floating-form  .long{
    width: 120px;
}
.floating-form input.input-field{
    width: 68%;
   
}

.floating-form input.input-field,
.floating-form .tel-number-field,
.floating-form .textarea-field,
 .floating-form .select-field{
    -webkit-transition: all 0.30s ease-in-out;
    -moz-transition: all 0.30s ease-in-out;
    -ms-transition: all 0.30s ease-in-out;
    -o-transition: all 0.30s ease-in-out; 
    box-sizing: border-box;
    -webkit-box-sizing: border-box;
    -moz-box-sizing: border-box;
    border: 1px solid #C2C2C2;
    box-shadow: 1px 1px 4px #EBEBEB;
    -moz-box-shadow: 1px 1px 4px #EBEBEB;
    -webkit-box-shadow: 1px 1px 4px #EBEBEB;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
    padding: 7px;
    outline: none;
}
.floating-form .input-field:focus,
.floating-form .tel-number-field:focus,
.floating-form .textarea-field:focus,  
.floating-form .select-field:focus{
    border: 1px solid #0C0;
}
.floating-form .textarea-field{
    height:100px;
    width: 68%;
}

.floating-form input[type="button"],
.floating-form input[type="submit"], .contact-opener {
    -moz-box-shadow: inset 0px 1px 0px 0px #3985B1;
    -webkit-box-shadow: inset 0px 1px 0px 0px #3985B1;
    box-shadow: inset 0px 1px 0px 0px #3985B1;
    background-color: #216288;
    border: 1px solid #17445E;
    display: inline-block;
    cursor: pointer;
    color: #FFFFFF;
    padding: 8px 18px;
    text-decoration: none;
    font: 12px Arial, Helvetica, sans-serif;
}

.floating-form input[type="submit"]
{
    border-radius: 5px;
    width: 157px;
}
.floating-form input[type="button"]:hover,
.floating-form input[type="submit"]:hover, .contact-opener {
    background: linear-gradient(to bottom, #2D77A2 5%, #337DA8 100%);
    background-color: #28739E;
}
.floating-form .success{
	background: #D8FFC0;
	padding: 5px 10px 5px 10px;
	margin: 0px 0px 5px 0px;
	border: none;
	font-weight: bold;
	color: #2E6800;
	border-left: 3px solid #2E6800;
}
.floating-form .error {
	background: #FFE8E8;
	padding: 5px 10px 5px 10px;
	margin: 0px 0px 5px 0px;
	border: none;
	font-weight: bold;
	color: #FF0000;
	border-left: 3px solid #FF0000;
}

/*Floating form css end*/

Найдем в common.js строку $(document).ready(function() {, добавим после нее код валидации и отправки формы через AJAX:

JavaScript

//// Floating form handler begin
var _scroll = true, _timer = false, _floatbox = $("#contact_form");
var _floatbox_opener = $(".contact-opener") ;
        
//Прячем или показываем форму
_floatbox_opener.click(function(){
    if (_floatbox.hasClass('visiable')){
        _floatbox.animate({"right":"-290px"}, {duration: 300}).removeClass('visiable');
    } else {
        _floatbox.animate({"right":"0px"},  {duration: 300}).addClass('visiable');
    }
});
	
//Эффект полета. Если нужен, раскомментируйте блок
//$(window).scroll(function(){
//    if(_scroll){
//        _floatbox.animate({"top": "30px"},{duration: 300});
//        _scroll = false;
//    }
//
//    if(_timer !== false){ clearTimeout(_timer); }           
//    _timer = setTimeout(function(){
//        _scroll = true; 
//        _floatbox.animate({"top": "10px"},{easing: "linear"}, {duration: 500});}, 400); 
//});
	
	
//Отправка при нажатии кнопки
$("#submit_btn").click(function() { 
    var proceed = true;
    //Простая валидация на стороне клиента
    //Пробегаем по каждому полю и просто меняем цвет рамки на красный, 
    //в случае ошибки заполнения
    $("#contact_form input[required=required]").each(function(){
        $(this).css('border-color',''); 
        //если поле пустое
        if(!$.trim($(this).val())){
            $(this).css('border-color','red'); //меняем цвета рамки на красный   
            proceed = false; //устанавливаем «стоп» флаг.
        }
    });
       
    if (proceed) {  //валидация прошла без ошибок, идем дальше.
        //извлекаем содержимое полей нашей формы, создаем объект
        post_data = {
            'user_name' : $('input[name=name]').val(), 
            'phone_number' : $('input[name=phone2]').val(), 
            'msg': $('textarea[name=message]').val()
        };
            
        //Передаем форму через AJAX методом POST
        $.post('/?route=ajax/feedback/ajaxSend', post_data, function(response){  
            if(response.type == 'error'){ //обрабатываем ответ сервера     
                output = '<div class="error">'+response.text+'</div>';
            } else {
                output = '<div class="success">'+response.text+'</div>';
                //очищаем поля фрмы
                $("#contact_form  input[required=required], #contact_form textarea").val(''); 
            }
            $("#contact_form #contact_results").hide().html(output).slideDown();
        }, 'json');
    }
});

//убираем красную рамку у полей, которые начал исправлять пользователь
$("#contact_form  input[required=required]").keyup(function() { 
    $(this).css('border-color',''); 
    $("#result").slideUp();
});

////floating form handler end

Теперь создадим папку ajax и контроллер feedback, следующего содержания:

Код контроллера

<?php
// catalog/controller/ajax/feedback.php
class ControllerAjaxFeedback extends Controller 
{
   
    public function ajaxSend() 
    {
        //проверяем была ли отправлена форма. Если нет, завершаем работу контроллера
        if ($this->request->server['REQUEST_METHOD'] !== 'POST'){
            return false;
        }
        
        //извлекаем имя домена из настроек CMS. 
        $parse = parse_url($this->config->get('config_url'));
        $domain = $parse['host']; 

        //поскольку ответ отправляем в формате JSON, необходимо установить заголовок
        $this->response->addHeader('Content-Type: application/json');

        //здесь валидация того, что форма отправлена именно через AJAX.
       //просто как дополнительная защита от спама
        if (!isset($this->request->server['HTTP_X_REQUESTED_WITH']) && 
            strtolower($this->request->server['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest'
        ) {
            $output = json_encode([
                'type'=>'error', 
                'text' => 'Sorry Request must be Ajax POST',
            ]);
            
            $this->response->setOutput($output);
            return false;
        } 
        
        $user_name = $this->request->post['user_name'];
        $phone_number = $this->request->post['phone_number'];
        $message = $this->request->post['msg'];

        //проверяем длину имени пользователя
        if (strlen($user_name) < 4) {
            
            $output = json_encode([
                'type'=>'error', 
                'text' => 'Слишком короткое имя',
            ]);
            
            $this->response->setOutput($output);
            return false;
        }

        if (!filter_var($phone_number, FILTER_SANITIZE_NUMBER_FLOAT)) {
            $output = json_encode([
                'type'=>'error', 
                'text' => 'Некорректный телефон',
            ]);

            $this->response->setOutput($output);
            return false;
        }

        // устанавливаем дополнительный параметр для корректного return-path
        $mail = new Mail(['parameter' => '-finfo@' . $domain]);
        //извлекаем различные данные из настроек магазина и заполняем в экземпляр mail.
        $mail->protocol = $this->config->get('config_mail_protocol');
        
        //тут смотрим есть ли в конфигах дополнительные параметры.
        //вероятно флаг –f или иной тюнинг был указан в админке, тогда перезаписываем.
        if (!empty($this->config->get('config_mail_parameter'))) {
            $mail->parameter = $this->config->get('config_mail_parameter');
        } 
        
        $mail->smtp_hostname = $this->config->get('config_mail_smtp_hostname');
        $mail->smtp_username = $this->config->get('config_mail_smtp_username');
        $mail->smtp_password = $this->config->get('config_mail_smtp_password');
        $mail->smtp_port = $this->config->get('config_mail_smtp_port');
        $mail->smtp_timeout = $this->config->get('config_mail_smtp_timeout');
        $mail->setTo($this->config->get('config_email'));
        
        //если отправляем через SMTP, то выставляем поле From в корректное значение.
        //иначе отправляем от имени info@вашдомен
        if ($mail->protocol !== 'mail') {
            $mail->setFrom($mail->smtp_username);
        } else {
            $mail->setFrom('info@' . $domain);
        }
        
        $mail->setSender($domain);
        $mail->setSubject('Сообщение с сайта ' . $domain);
        
        $mail->setText('Имя отправителя: ' 
                       . $user_name 
                       . "\r\nТелефон: " 
                       . $phone_number 
                       . "\r\nСообщение: " 
                       . $message);
        
        $mail->send();
        
        $this->response->setOutput(json_encode([
            'type'=>'message', 
            'text' => 'Ваше сообщение отправлено!',
        ]));
         
    }
}

Адрес, на который отправляются письма, берем из настроек OpenCart и другие настройки, связанные с почтой. Контроллер работает как в режиме «Mail» так и «SMTP».



Данное решение тщательно протестировано на версии OpenCart 2.3.0.2 (русская сборка).

Чтобы ваши письма не попадали в СПАМ, воспользуйтесь рекомендациями из ранее опубликованной статьи

  • Rasheed:

    Сделал все как написано в статье, но при нажатии на кнопку отправить ничего не происходит, поля все заполнены, OpenCart 2.3.

    • root4root aka admin:

      Как ничего не происходит, вообще глухо? Или есть надпись, что сообщение отправлено, но фактически на почту не приходит?

      Если ошибка (скорее опечатка тогда уж) всё-таки закралась во время оформления статьи, то я ее обязательно исправлю. Но это очень странно, код лично проверял, более того, — он работает на нескольких сайтах. Предоставьте, пожалуйста, больше информации по диагностике. Что в консоли браузера, например. Запрос проходит? Что отвечает сервер?

      • Артем:

        Наткнулся на данную статью в 2020, ошибка такая, что нужно указывать e-mail отправителя и получателя, в данном случае отправляется письмо самому себе, добавляется после $mail->smtp_timeout = $this->config->get(‘config_mail_smtp_timeout’);
        $mail->setTo($this->config->get(‘config_email’));
        $mail->setFrom($this->config->get(‘config_email’));

  • Nikolay:

    Привет, спасибо за статью, перепиливаю под себя, но сейчас после всех заливки всех ф-в на сервер мне выдаёт что post_data не определён http://prntscr.com/iumurq

  • Nikolay:

    добавил переменную массивом post_data = []; и вроде 200, но на почту ничего не пришло, хотя ставил свою дополнительной http://prntscr.com/iumwdz

    • root4root aka admin:

      Нет, там объект должен быть, а не массив. На дополнительную почту не должно приходить сообщение, только на основную. Чтобы добавить возможность, нужно примерно так в PHP контроллере:

      $mail->setTo(
          array(
              $this->config->get('config_email'),
              $this->config->get('config_alert_emails')
          )
      );

      Почитайте класс Mail, который находится system/library/mail.php, вам многое станет понятно. Они его периодически изменяют, кстати.

      Если письма не приходят, проверьте с помощью сайта https://www.mail-tester.com, там разберетесь чего как. Дело в том, что сейчас действуют агрессивные анти-спам политики. Часто письма не попадают даже в соответствующую папку на принимающем сервере, а просто молча удаляются им. Почитайте, у меня есть по этому поводу статья: https://www.wisereport.ru/nonspam-emails

  • Rasul:

    Спасибо! Всё четко работает. Сделал форму без всплывающей формы и на опенкарт 1.5 Код подошёл

  • Денис:

    Пытаюсь добавить пару полей в форму.
    Добавил в скрипте в post_data ‘company’ : $(‘input[name=company]’).val(),
    добавил в php $company = $this->request->post[‘company’];

    но при добавлении переменной $company в теле письма — там пусто 🙁

    Можете подсказать?

  • arm923i:

    Notice: Array to string conversion in /home/mp2016/master-prof.com/www/system/library/mail.php on line 31Notice: Array to string conversion in /home/mp2016/master-prof.com/www/system/library/mail.php on line 36Notice: Error: Could not load mail adaptor Array! in /home/mp2016/master-prof.com/www/system/library/mail.php on line 36

    Подскажите что не так? Все по инструкции сделал. OC Version 3.0.3.2 (rs.2)

Добавить комментарий