Yii2. Отправка данных AJAX
03.03.2019
Теги: AJAX • JavaScript • PHP • POST • Web-разработка • Yii2 • Теория • Фреймворк
На сегодняшний день отправка данных на сервер без перезагрузки страницы уже стала стандартом де факто. За счет этого уменьшается время отклика, да и выглядит это достаточно привлекательно, сопровождаясь красивыми анимационными эффектами. Давайте создадим форму обратной связи и посмотрим, как можно отправить данные на сервер без перезагрузки страницы.
Создаем форму обратной связи
Для начала создадим новый action
в контроллере SiteController
:
<?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
:
<?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-шаблон:
<?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:
<?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); /* ... */
И внесем изменения в метод контроллера:
<?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]); } /* ... */ }
Изменения получились минимальные. Вот что было раньше
/* * Если пришли post-данные, загружаем их в модель... */ if ($model->load(Yii::$app->request->post())) { // ...и проверяем эти данные if ( ! $model->validate()) { /* * Данные не прошли валидацию */ } else { /* * Данные прошли валидацию */ Yii::$app->session->setFlash( 'feedback-success', true ); } return $this->refresh(); }
А вот что стало сейчас
/* * Если пришли 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-токена будет добавлено в форму без нашего участия. В противном случае, нужно позаботиться о добавлении этого поля самому, чтобы не получить ошибку
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
части документа:
<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
, получить имя и значение токена можно еще так:
var param = yii.getCsrfParam(); var token = yii.getCsrfToken();
Проверку CSRF-токена можно отключить, но делать это крайне нежелательно:
<?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() { /* ... */ } }
Поиск: AJAX • JavaScript • PHP • POST • Web-разработка • Yii2 • Фреймворк • Framework