Отправка данных на сервер без перезагрузки страницы уже стала стандартом де факто. За счет этого уменьшается время отклика, да и выглядит это достаточно привлекательно, сопровождаясь красивыми анимационными эффектами. Создадим форму обратной связи и изучим, как можно отправить данные на сервер без перезагрузки страницы.
Форма обратной связи
Для начала создадим новый action в контроллере SiteController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | <?php namespace app\controllers; use Yii; use app\models\Feedback; use yii\web\Controller; class SiteController extends Controller { /*...*/ public function actionFeedback() { $model = new Feedback(); /* * Если пришли post-данные, загружаем их в модель... */ if ( $model ->load(Yii:: $app ->request->post())) { // ...и проверяем эти данные if ( ! $model ->validate()) { /* * Данные не прошли валидацию */ Yii:: $app ->session->setFlash( 'feedback-success' , false ); // сохраняем в сессии введенные пользователем данные Yii:: $app ->session->setFlash( 'feedback-data' , [ 'name' => $model ->name, 'email' => $model ->email, 'body' => $model ->body ] ); /* * Сохраняем в сессии массив сообщений об ошибках. Массив имеет вид * [ * 'name' => [ * 'Поле «Ваше имя» обязательно для заполнения', * ], * 'email' => [ * 'Поле «Ваш email» обязательно для заполнения', * 'Поле «Ваш email» должно быть адресом почты' * ] * ] */ Yii:: $app ->session->setFlash( 'feedback-errors' , $model ->getErrors() ); } else { /* * Данные прошли валидацию */ // отправляем письмо на почту администратора $textBody = 'Имя: ' . strip_tags ( $model ->name) . PHP_EOL; $textBody .= 'Почта: ' . strip_tags ( $model ->email) . PHP_EOL . PHP_EOL; $textBody .= 'Сообщение: ' . PHP_EOL . strip_tags ( $model ->body); $htmlBody = '<p><b>Имя</b>: ' . strip_tags ( $model ->name) . '</p>' ; $htmlBody .= '<p><b>Почта</b>: ' . strip_tags ( $model ->email) . '</p>' ; $htmlBody .= '<p><b>Сообщение</b>:</p>' ; $htmlBody .= '<p>' . nl2br ( strip_tags ( $model ->body)) . '</p>' ; Yii:: $app ->mailer->compose() ->setFrom(Yii:: $app ->params[ 'senderEmail' ]) ->setTo(Yii:: $app ->params[ 'adminEmail' ]) ->setSubject( 'Заполнена форма обратной связи' ) ->setTextBody( $textBody ) ->setHtmlBody( $htmlBody ) ->send(); // данные прошли валидацию, отмечаем этот факт Yii:: $app ->session->setFlash( 'feedback-success' , true ); } // выполняем редирект, чтобы избежать повторной отправки формы return $this ->refresh(); } return $this ->render( 'feedback' , [ 'model' => $model ]); } /*...*/ } |
Для проверки данных формы создаем класс модели Feedback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?php namespace app\models; use yii\base\Model; class Feedback extends Model { public $name ; public $email ; public $body ; public function attributeLabels() { return [ 'name' => 'Ваше имя' , 'email' => 'Ваш e-mail' , 'body' => 'Ваше сообщение' , ]; } public function rules() { return [ // удалить пробелы для всех трех полей формы [[ 'name' , 'email' , 'body' ], 'trim' ], // поле name обязательно для заполнения [ 'name' , 'required' , 'message' => 'Поле «Ваше имя» обязательно для заполнения' ], // поле email обязательно для заполнения [ 'email' , 'required' , 'message' => 'Поле «Ваш email» обязательно для заполнения' ], // поле email должно быть корректным адресом почты [ 'email' , 'email' , 'message' => 'Поле «Ваш email» должно быть адресом почты' ], // поле body обязательно для заполнения [ 'body' , 'required' , 'message' => 'Поле «Сообщение» обязательно для заполнения' ], // поля name и email должны быть не более 50 символов [ [ 'name' , 'email' ], 'string' , 'max' => 50, 'tooLong' => 'Поле должно быть длиной не более 50 символов' ], // поле body должно быть не более 1000 символов [ 'body' , 'string' , 'max' => 1000, 'tooLong' => 'Сообщение должно быть длиной не более 1000 символов' ], ]; } } |
Следующим шагом, создаем view-шаблон:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | <?php use yii\helpers\Html; use yii\bootstrap\ActiveForm; $this ->title = 'Обратная связь' ; /* * Если данные формы не прошли валидацию, получаем из сессии сохраненные * данные, чтобы заполнить ими поля формы, не заставляя пользователя * заполнять форму повторно */ $name = '' ; $email = '' ; $body = '' ; if (Yii:: $app ->session->hasFlash( 'feedback-data' )) { $data = Yii:: $app ->session->getFlash( 'feedback-data' ); $name = Html::encode( $data [ 'name' ]); $email = Html::encode( $data [ 'email' ]); $body = Html::encode( $data [ 'body' ]); } ?> <div class = "container" > <?php $success = false; if (Yii:: $app ->session->hasFlash( 'feedback-success' )) { $success = Yii:: $app ->session->getFlash( 'feedback-success' ); } ?> <div id= "response" > <?php if (! $success ): ?> <?php if (Yii:: $app ->session->hasFlash( 'feedback-errors' )): ?> <div class = "alert alert-warning alert-dismissible" role= "alert" > <button type= "button" class = "close" data-dismiss= "alert" aria-label= "Закрыть" > <span aria-hidden= "true" >×</span> </button> <p>При заполнении формы допущены ошибки</p> <?php $allErrors = Yii:: $app ->session->getFlash( 'feedback-errors' ); ?> <ul> <?php foreach ( $allErrors as $errors ): ?> <?php foreach ( $errors as $error ): ?> <li><?= $error ; ?></li> <?php endforeach ; ?> <?php endforeach ; ?> </ul> </div> <?php endif ; ?> <?php else : ?> <div class = "alert alert-success alert-dismissible" role= "alert" > <button type= "button" class = "close" data-dismiss= "alert" aria-label= "Закрыть" > <span aria-hidden= "true" >×</span> </button> <p>Ваше сообщение успешно отправлено</p> </div> <?php endif ; ?> </div> <?php $form = ActiveForm::begin([ 'id' => 'feedback' , 'class' => 'form-horizontal' ]); ?> <?= $form ->field( $model , 'name' )->textInput([ 'value' => $name ]); ?> <?= $form ->field( $model , 'email' )->input( 'email' , [ 'value' => $email ]); ?> <?= $form ->field( $model , 'body' )->textarea([ 'rows' => 5, 'value' => $body ]); ?> <div class = "form-group" > <?= Html::submitButton( 'Отправить' , [ 'class' => 'btn btn-primary' ]) ?> </div> <?php ActiveForm:: end (); ?> </div> |
Отправка формы с использованием AJAX
Для начала зарегистрируем в view-шаблоне js-код, который будет отправлять данные формы с использованием объекта XmlHttpRequest:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php use yii\helpers\Html; use yii\bootstrap\ActiveForm; $js = <<<JS $( '#feedback' ).on( 'beforeSubmit' , function () { var form = $(this); var data = form.serialize(); // отправляем данные на сервер $.ajax({ url: form.attr( 'action' ), type: form.attr( 'method' ), data: data }) .done( function (data) { if (data.success) { // данные прошли валидацию, сообщение было отправлено $( '#response' ).html(data.message); form.children( '.has-success' ).removeClass( 'has-success' ); form[0].reset(); } }) .fail( function () { alert( 'Произошла ошибка при отправке данных!' ); }) return false; // отменяем отправку данных формы }); JS; $this ->registerJs( $js , $this ::POS_READY); /*...*/ |
И внесем изменения в метод контроллера:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | <?php namespace app\controllers; use Yii; use yii\web\Controller; class SiteController extends Controller { /*...*/ public function actionFeedback() { $model = new Feedback(); /* * Если пришли post-данные, загружаем их в модель... */ if ( $model ->load(Yii:: $app ->request->post())) { // ...и проверяем эти данные if ( ! $model ->validate()) { /* * Данные не прошли валидацию */ Yii:: $app ->session->setFlash( 'feedback-success' , false ); // сохраняем в сессии введенные пользователем данные Yii:: $app ->session->setFlash( 'feedback-data' , [ 'name' => $model ->name, 'email' => $model ->email, 'body' => $model ->body ] ); /* * Сохраняем в сессии массив сообщений об ошибках. Массив имеет вид * [ * 'name' => [ * 'Поле «Ваше имя» обязательно для заполнения', * ], * 'email' => [ * 'Поле «Ваш email» обязательно для заполнения', * 'Поле «Ваш email» должно быть адресом почты' * ] * ] */ Yii:: $app ->session->setFlash( 'feedback-errors' , $model ->getErrors() ); } else { /* * Данные прошли валидацию */ // отправляем письмо на почту администратора $textBody = 'Имя: ' . strip_tags ( $model ->name) . PHP_EOL; $textBody .= 'Почта: ' . strip_tags ( $model ->email) . PHP_EOL . PHP_EOL; $textBody .= 'Сообщение: ' . PHP_EOL . strip_tags ( $model ->body); $htmlBody = '<p><b>Имя</b>: ' . strip_tags ( $model ->name) . '</p>' ; $htmlBody .= '<p><b>Почта</b>: ' . strip_tags ( $model ->email) . '</p>' ; $htmlBody .= '<p><b>Сообщение</b>:</p>' ; $htmlBody .= '<p>' . nl2br ( strip_tags ( $model ->body)) . '</p>' ; Yii:: $app ->mailer->compose() ->setFrom(Yii:: $app ->params[ 'senderEmail' ]) ->setTo(Yii:: $app ->params[ 'adminEmail' ]) ->setSubject( 'Заполнена форма обратной связи' ) ->setTextBody( $textBody ) ->setHtmlBody( $htmlBody ) ->send(); // это обычный POST-запрос или это AJAX-запрос? if (Yii:: $app ->request->isAjax) { $message = <<<HTML <div class = "alert alert-success alert-dismissible" role= "alert" > <button type= "button" class = "close" data-dismiss= "alert" aria-label= "Закрыть" > <span aria-hidden= "true" >×</span> </button> <p>Ваше сообщение успешно отправлено</p> </div> HTML; Yii:: $app ->response->format = Response::FORMAT_JSON; $response = [ 'success' => true, 'message' => $message ]; return $response ; } else { // данные прошли валидацию, отмечаем этот факт Yii:: $app ->session->setFlash( 'feedback-success' , true ); } } // выполняем редирект, чтобы избежать повторной отправки формы return $this ->refresh(); } return $this ->render( 'feedback' , [ 'model' => $model ]); } /*...*/ } |
Изменения получились минимальные. Вот что было раньше
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | /* * Если пришли post-данные, загружаем их в модель... */ if ( $model ->load(Yii:: $app ->request->post())) { // ...и проверяем эти данные if ( ! $model ->validate()) { /* * Данные не прошли валидацию */ } else { /* * Данные прошли валидацию */ Yii:: $app ->session->setFlash( 'feedback-success' , true ); } return $this ->refresh(); } |
А вот что стало сейчас
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /* * Если пришли post-данные, загружаем их в модель... */ if ( $model ->load(Yii:: $app ->request->post())) { // ...и проверяем эти данные if ( ! $model ->validate()) { /* * Данные не прошли валидацию */ } else { /* * Данные прошли валидацию */ // это обычный POST-запрос или это AJAX-запрос? if (Yii:: $app ->request->isAjax) { $message = 'Ваше сообщение успешно отправлено' ; Yii:: $app ->response->format = Response::FORMAT_JSON; $response = [ 'success' => true, 'message' => $message ]; return $response ; } else { // данные прошли валидацию, отмечаем этот факт Yii:: $app ->session->setFlash( 'feedback-success' , true ); } } return $this ->refresh(); } |
Использование CSRF-токена
Если для построения формы используется ActiveForm, то скрытое поле со значением CSRF-токена будет добавлено в форму без нашего участия. В противном случае, нужно позаботиться о добавлении этого поля самому, чтобы не получить ошибку
1 2 3 4 5 | Bad Request (#400): Не удалось проверить переданные данные. <form action= "/some/handler" method= "post" > <input type= "hidden" name= "<?= Yii::$app->request->csrfParam; ?>" value= "<?= Yii::$app->request->csrfToken; ?>" /> .......... </form> |
При ajax-отправке данных значение токена можно взять из head части документа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <head> <meta charset= "UTF-8" > <meta http-equiv= "X-UA-Compatible" content= "IE=edge" > <meta name= "viewport" content= "width=device-width, initial-scale=1.0" > <title>Обратная связь</title> <meta name= "csrf-param" content= "_csrf" > <meta name= "csrf-token" content= ".........." > .......... </head> var data = { 'value' : 'some value' }; var param = $( 'meta[name=csrf-param]' ).attr( 'content' ); var token = $( 'meta[name=csrf-token]' ).attr( 'content' ); data[param] = token; $.ajax({ url: '/some/handler' , type: 'post' , data: data, success: function (response) { /*...*/ } }); |
Если к странице подключен yii.js, получить имя и значение токена можно еще так:
1 2 | var param = yii.getCsrfParam(); var token = yii.getCsrfToken(); |
Проверку CSRF-токена можно отключить, но делать это крайне нежелательно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php namespace app\controllers; use Yii; use yii\web\Controller; class SiteController extends Controller { public function beforeAction( $action ) { if ( $action ->id == 'feedback' ) { // отключаем проверку CSRF-токена $this ->enableCsrfValidation = false; } return parent::beforeAction( $action ); } public function actionFeedback() { /*...*/ } } |