В этом уроке мы рассмотрим реализацию асинхронной отправки данных из формы на примере простых комментариев (условный пример). При заполнении поля для текста комментария и нажатия на кнопку отправки, комментарий записывается в БД и без перезагрузки страницы выводится под формой.
Во втором примере мы с помощью jQuery ajax прикрепим изображение к модели нашей новости из прошлого урока.
Простая отправка формы асинхронно
Простая отправка формы асинхронно на примере добавления комментариев.
Миграция:
public function up()
{
$this->createTable('{{%comment}}', [
'id' => $this->primaryKey(),
'comment' => $this->text()
], $tableOptions);
}
public function down()
{
$this->dropTable('{{%comment}}');
}
Класс AjaxCommentForm
:
class AjaxCommentForm extends Model
{
public $comment;
public function rules()
{
return [
['comment', 'string'],
];
}
}
Класс Comment
:
class Comment extends ActiveRecord
{
public static function tableName()
{
return '{{%comment}}';
}
public static function getComments()
{
return self::find()->all();
}
}
Представление:
<?php $form = ActiveForm::begin([
'enableClientValidation' => true,
'enableAjaxValidation' => false,
'action' => Url::to(['site/index']),
'method' => 'post',
'options' => ['id' => 'form']
]) ?>
<?= $form->field($model, 'comment')->textarea(['rows' => 5]); ?>
<?= Html::submitButton("Submit", ['class' => "btn btn-default"]); ?>
<?php ActiveForm::end() ?>
<div id="process">
<img src="/img/loading.gif" alt="Loading">
</div>
<h2>Comments</h2>
<div id="comments">
<?
/**
* @var $comments
* @var $item \frontend\models\Comment
*/
foreach ($comments as $item):
?>
<div class="comment"><?= $item->comment ?></div>
<? endforeach; ?>
</div>
<?php
$js = <<<JS
$('#form').on('beforeSubmit', function(){
var form = $(this),
data = $(this).serialize();
$.ajax({
url: form.attr("action"),
type: form.attr("method"),
data: data,
beforeSend: function(){
$('#process').fadeIn();
},
success: function(data){
form[0].reset();
$("#comments").append('<div class="comment">'+ data.comment +'</div>');
$('#process').fadeOut();
},
error: function(){
$('#process').fadeOut();
alert('Error!');
}
});
return false;
}).on('submit', function(e){
e.preventDefault();
});
JS;
$this->registerJs($js);
?>
Стили для #process
:
#process{
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(255, 255, 255, 1);
z-index: 1;
display: none;
}
#process img{
position: absolute;
top: 80px;
right: 30px;
z-index: 2;
}
Метод контроллера:
public function actionIndex()
{
$form = new AjaxCommentForm();
if(Yii::$app->request->isAjax){
Yii::$app->response->format = Response::FORMAT_JSON;
if($form->load(Yii::$app->request->post()) && $form->validate()){
$commentModel = new Comment();
$commentModel->comment = $form->comment;
if ($commentModel->save()) {
return $data = [
'success' => true,
'comment' => $form->comment,
];
}
}
}
return $this->render('index', [
'model' => $form,
'comments' => Comment::getComments()
]);
}
Асинхронная загрузка изображения для новости
В этом примере мы реализуем загрузку изображения на сервер с помощью Ajax. Возьмём за основу код из примера загрузки изображения для новости в новостной ленте.
Класс News
:
class News extends ActiveRecord
{
public static function tableName()
{
return '{{%news}}';
}
public function getImagePath()
{
if ($this->image)
return $this->getImage($this->image);
return 'https://via.placeholder.com/300x200';
}
private function getImage(string $filename): string
{
return Yii::$app->params['uploadHostInfo'] . 'news/' . $filename;
}
public function beforeDelete()
{
$this->deleteImage();
return parent::beforeDelete();
}
public function deleteImage()
{
$form = new NewsImageForm();
$form->deleteCurrentImage($this->image);
}
}
Класс NewsForm
:
class NewsForm extends Model{
public $name;
public $content;
public $slug;
public $created_at;
private $_model;
public function __construct(News $model = null, $config = []) {
if ($model) {
$this->name = $model->name;
$this->content = $model->content;
$this->slug = $model->slug;
$this->created_at = $model->created_at;
$this->_model = $model;
}
parent::__construct($config);
}
public function rules()
{
return [
[['name'], 'required'],
[['name', 'slug'], 'string', 'max' => 255],
['content', 'string'],
['slug', SlugValidator::class],
[['name', 'slug'], 'unique',
'targetClass' => News::class,
'filter' => $this->_model ? ['<>', 'id', $this->_model->id] : null
]
];
}
}
Класс NewsImageForm
:
class NewsImageForm extends Model{
/**
* @var UploadedFile
*/
public $image;
public function rules()
{
return [
['image', 'image',
'extensions' => ['jpg', 'jpeg', 'png'],
'checkExtensionByMimeType' => true,
'maxSize' => 512000, // 500 килобайт = 500 * 1024 байта = 512 000 байт
'tooBig' => 'Limit is 500KB'
]
];
}
public function uploadImage(UploadedFile $image, $currentImage = null)
{
if (!is_null($currentImage))
$this->deleteCurrentImage($currentImage);
$this->image = $image;
if($this->validate())
return $this->saveImage();
return false;
}
private function getUploadPath()
{
return Yii::$app->params['uploadPath'] . 'news/';
}
/**
* @return string
*/
public function generateFileName(): string
{
do {
$name = substr(md5(microtime() . rand(0, 1000)), 0, 20);
$file = strtolower($name .'.'. $this->image->extension);
} while (file_exists($file));
return $file;
}
public function deleteCurrentImage($currentImage)
{
if ($currentImage && $this->fileExists($currentImage)) {
unlink($this->getUploadPath() . $currentImage);
}
}
public function fileExists($currentFile): bool
{
$file = $currentFile ? $this->getUploadPath() . $currentFile : null;
return file_exists($file);
}
public function saveImage(): string
{
$filename = $this->generateFilename();
$this->image->saveAs($this->getUploadPath() . $filename);
return $filename;
}
}
Представление (страница новости):
<?php
use common\models\News;
use yii\helpers\Html;
use yii\widgets\DetailView;
/* @var $this yii\web\View */
/* @var $model common\models\News */
/* @var $imageForm \common\forms\NewsImageForm */
$this->title = $model->name;
$this->params['breadcrumbs'][] = ['label' => 'News', 'url' => ['index']];
$this->params['breadcrumbs'][] = $this->title;
\yii\web\YiiAsset::register($this);
?>
<div class="new-view">
<h1><?= Html::encode($this->title) ?></h1>
<p>
<?= Html::a('Update', ['update', 'id' => $model->id], ['class' => 'btn btn-primary']) ?>
<?= Html::a('Delete', ['delete', 'id' => $model->id], [
'class' => 'btn btn-danger',
'data' => [
'confirm' => 'Are you sure you want to delete this item?',
'method' => 'post'
]
]) ?>
</p>
<?= DetailView::widget([
'model' => $model,
'attributes' => [
'id',
'name',
'slug',
'content:raw',
'created_at:date',
[
'value' => function (News $model) {
return Html::img($model->getImagePath(), ['width' => 200, 'alt' => $model->name, 'id' => 'upload-image']);
},
'label' => 'Image',
'format' => 'raw'
]
]
]) ?>
</div>
<?php
use yii\helpers\Url;
use yii\widgets\ActiveForm;
?>
<?php $form = ActiveForm::begin([
'enableClientValidation' => true,
'enableAjaxValidation' => false,
'action' => Url::to(['ajax-upload-image', 'id' => $model->id]),
'method' => 'post',
'options' => ['id' => 'form']
]) ?>
<?= $form->field($imageForm, 'image')->fileInput(['id' => 'form-image']); ?>
<p>
<?= Html::submitButton('Save Image', ['class' => 'btn btn-default', 'id' => 'btn-save-image', 'disabled' => true]) ?>
<? if ($model->image): ?>
<?= Html::a('Delete Image', ['ajax-delete-image', 'id' => $model->id], [
'class' => 'btn btn-warning',
'id' => 'delete-image',
'data' => ['method' => 'post']
]) ?>
<? endif; ?>
</p>
<?php ActiveForm::end() ?>
<?php
$js = <<<JS
// Загрузка изображения
var btnSaveImage = $('#btn-save-image'),
btnDeleteImage = $('#delete-image'),
inputImage = $('#form-image'),
image = $("#upload-image"),
process = $('#process');
inputImage.on('change', function() {
btnSaveImage.prop('disabled', false);
});
btnSaveImage.on('click', function(e){
e.preventDefault();
var form = $('#form');
var formData = new FormData(form[0]);
$.ajax({
url: form.attr("action"),
type: form.attr("method"),
data: formData,
dataType : 'text',
processData: false,
contentType: false,
cache: false,
beforeSend: function(){
process.fadeIn('fast');
},
success: function(data){
form[0].reset();
data = JSON.parse(data);
btnSaveImage.prop('disabled', true);
btnDeleteImage.fadeIn();
image.attr('src', data.image);
process.delay(1000).fadeOut();
},
error: function(){
process.delay(1000).fadeOut();
alert('Error!');
}
});
return false;
});
// Удаление изображения
btnDeleteImage.on("click", function(e) {
e.preventDefault();
var res = confirm('Вы действительно хотите удалить текущее изображение?');
if(!res) return false;
$.ajax({
url: $(this).attr("href"),
type: $(this).data("method"),
dataType : 'text',
processData: false,
contentType: false,
beforeSend: function(){
process.fadeIn('fast');
},
success: function(data){
data = JSON.parse(data);
btnSaveImage.prop('disabled', true);
image.attr('src', data.image);
btnDeleteImage.fadeOut();
process.delay(1000).fadeOut();
},
error: function(){
process.delay(1000).fadeOut();
alert('Error!');
}
});
return false;
});
JS;
$this->registerJs($js);
?>
<div id="process">
<img src="/img/loading.gif" alt="Loading">
</div>
Контроллер:
// Получаем модель
protected function findModel($id)
{
if (($model = News::findOne($id)) !== null) {
return $model;
}
throw new NotFoundHttpException('The requested page does not exist.');
}
// Страница новости
public function actionView($id)
{
return $this->render('view', [
'model' => $this->findModel($id),
'imageForm' => new NewsImageForm()
]);
}
// Создание новости
public function actionCreate()
{
$model = new News();
$modelForm = new NewsForm();
if ($modelForm->load(Yii::$app->request->post()) && $modelForm->validate()) {
$model->name = $modelForm->name;
$model->content = $modelForm->content;
$model->slug = $modelForm->slug ?: Inflector::slug($model->name);
$model->created_at = $modelForm->created_at ?: time();
if($model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
}
}
return $this->render('create', [
'model' => $model,
'modelForm' => $modelForm
]);
}
// Редактирование новости
public function actionUpdate($id)
{
$model = $this->findModel($id);
$modelForm = new NewsForm($model);
if ($modelForm->load(Yii::$app->request->post()) && $modelForm->validate()) {
$model->name = $modelForm->name;
$model->content = $modelForm->content;
$model->slug = $modelForm->slug ?: Inflector::slug($model->name);
if($model->save()) {
Yii::$app->session->setFlash('success', 'Все прошло удачно');
return $this->redirect(['view', 'id' => $model->id]);
}
}
return $this->render('update', [
'model' => $model,
'modelForm' => $modelForm,
]);
}
// Загрузка изображения
public function actionAjaxUploadImage($id)
{
Yii::$app->response->format = Response::FORMAT_JSON;
if(Yii::$app->request->isAjax){
$model = $this->findModel($id);
$form = new NewsImageForm();
$form->image = UploadedFile::getInstance($form, 'image');
if($form->image && $form->validate()){
$model->image = $form->uploadImage($form->image, $model->image);
if ($model->save(false)) {
return $data = [
'success' => true,
'image' => $model->getImagePath()
];
}
}
}
return $data = [
'success' => false
];
}
// Удаление изображения
public function actionAjaxDeleteImage($id)
{
Yii::$app->response->format = Response::FORMAT_JSON;
if (Yii::$app->request->isAjax) {
$model = $this->findModel($id);
$model->deleteImage();
$model->image = '';
if ($model->save(false)) {
return $data = [
'success' => true,
'image' => $model->getImagePath()
];
}
}
return $data = [
'success' => false
];
}
Комментарии (0)
Пока еще не было комментариев ✍️