Блог → Yii: удобный CRUD на диалоговых окнах

DialogCRUD

Наверное, я не первый человек, который задался вопросом: "Как бы переделать стандартный CRUD сгенерированный Gii, чтобы он работал на диалоговых окнах?". Оказывается, для этих целей даже создано готовое расширение eupdatedialog. Но мой способ, которым я хочу поделиться с читателями своего блога будет несколько иным. В этой статье я расскажу о том, как можно реализовать CRUD на диалоговых окнах, внося минимальные изменения в код и поведение фрэймворка. К слову сказать, при отключенном, javascript в браузере вообще не будет никаких отличий от действий стандартного кода Gii. Но вот при включенном javascript можно будет насладиться всеми прелестями и удобствами создания и редактирования записей на диалоговых окнах. Более того, я расскажу, как всего этого добиться не написав не единой строчки кода! Интересно? Тогда добро пожаловать под кат.

Итак, создадим в нашей базе следующую таблицу.


CREATE TABLE `people` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `surname` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

С помощью Gii сгенерируем к этой таблице модель и CRUD.

Изменим действия контроллера Create и Update:


public function actionCreate(){
		
	$model=new People;
	
	// Uncomment the following line if AJAX validation is needed
	// $this->performAjaxValidation($model);
	
	if(isset($_POST['People'])){
		$model->attributes=$_POST['People'];
		if($model->save()){
			if(Yii::app()->request->isAjaxRequest){
				echo 'success';
				Yii::app()->end();
			}
			else {
				$this->redirect(array('view','id'=>$model->id));
			}
		}
	}
	if(Yii::app()->request->isAjaxRequest)
		$this->renderPartial('create',array('model'=>$model), false, true);
	else
		$this->render('create',array('model'=>$model));

}

public function actionUpdate($id){
		
	$model=$this->loadModel($id);

	// Uncomment the following line if AJAX validation is needed
	// $this->performAjaxValidation($model);

	if(isset($_POST['People'])){
		$model->attributes=$_POST['People'];
		if($model->save()){
			if(Yii::app()->request->isAjaxRequest){
				echo 'success';
				Yii::app()->end();
			}
			else
				$this->redirect(array('view','id'=>$model->id));
		}
	}
	if(Yii::app()->request->isAjaxRequest)
		$this->renderPartial('update',array('model'=>$model), false, true);
	else
		$this->render('update',array('model'=>$model));

}	

Здесь мы проверяем тип запроса. Если это обычный запрос, то выполняются стандартные действия сгенерированные Gii. В случае если это AJAX запрос мы отдаем только форму через renderPartial, также если модель проходит валидацию и сохраняется успешно отдаем сообщение success.

Внесем изменения в файл представления admin.php:


<?php
$this->breadcrumbs=array(
	'Peoples'=>array('index'),
	'Manage',
);

$this->menu=array(
	array('label'=>'List Champion', 'url'=>array('index')),
	array('label'=>'Create Champion', 'url'=>array('create'), 'linkOptions'=>array(
		'ajax' => array(
			'url'=>$this->createUrl('create'),
			'success'=>'function(r){$("#create").html(r).dialog("open"); return false;}', 
		),
	)),
);

$this->beginWidget('zii.widgets.jui.CJuiDialog',array(
        'id'=>'create',
        'options'=>array(
			'title'=>'Create People',
			'autoOpen'=>false,
			'modal'=>true,
			'width'=>'auto',
			'height'=>'auto',
			'resizable'=>'false',
		),
	));
$this->endWidget();

$this->beginWidget('zii.widgets.jui.CJuiDialog',array(
        'id'=>'update',
        'options'=>array(
			'title'=>'Update People',
			'autoOpen'=>false,
			'modal'=>true,
			'width'=>'auto',
			'height'=>'auto',
			'resizable'=>'false',
		),
	));
$this->endWidget();

$updateDialog =<<<'EOT'
function() {
	var url = $(this).attr('href');
    $.get(url, function(r){
        $("#update").html(r).dialog("open");
    });
    return false;
}
EOT;

Yii::app()->clientScript->registerScript('search', "
$('.search-button').click(function(){
	$('.search-form').toggle();
	return false;
});
$('.search-form form').submit(function(){
	$.fn.yiiGridView.update('people-grid', {
		data: $(this).serialize()
	});
	return false;
});
");
?>

<h1>Manage Peoples</h1>

<p>
You may optionally enter a comparison operator (<b><</b>, <b><=</b>, <b>></b>, <b>>=</b>, <b><></b>
or <b>=</b>) at the beginning of each of your search values to specify how the comparison should be done.
</p>

<?php echo CHtml::link('Advanced Search','#',array('class'=>'search-button')); ?>
<div class="search-form" style="display:none">
<?php $this->renderPartial('_search',array(
	'model'=>$model,
)); ?>
</div><!-- search-form -->

<?php $this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'people-grid',
	'ajaxUpdate'=>false,
	'dataProvider'=>$model->search(),
	'filter'=>$model,
	'columns'=>array(
		'id',
		'name',
		'surname',
		array(
			'class'=>'CButtonColumn',
			'buttons' => array(
				'update' => array(
					'click'=>$updateDialog
				),
			), 
		),
	),
)); ?>

Здесь мы немного видоизменили меню. Из обычной ссылки Create мы зделали AJAX ссылку при клике на которой, будет открываться диалоговое окно для добавления новой записи. Еще мы добавили два виджета диалоговых окон создания и редактирования записи. Также мы внесли изменения в виджет CGridView. При клике на кнопке Update теперь будет открываться диалоговое окно для редактирования записи. Также мы отключили обновления CGridView через AJAX. Дело в том, что при успешном сохранении записи наша страница будет перезагружена, и отключенный AJAX позволит нам сохранить текущее состоянии грида, т.к в этом случае оно будет передаваться через URL.

Внесем изменения в файлы представлений create.php и update.php:


<?php if (!Yii::app()->request->isAjaxRequest): ?>

<?php
$this->breadcrumbs=array(
	'Peoples'=>array('index'),
	'Create',
);

$this->menu=array(
	array('label'=>'List People', 'url'=>array('index')),
	array('label'=>'Manage People', 'url'=>array('admin')),
);
?>

<h1>Create People</h1>

<?php endif; ?>

<?php echo $this->renderPartial('_form', array('model'=>$model)); ?>

<?php if (!Yii::app()->request->isAjaxRequest): ?>

<?php
$this->breadcrumbs=array(
	'Peoples'=>array('index'),
	$model->name=>array('view','id'=>$model->id),
	'Update',
);

$this->menu=array(
	array('label'=>'List People', 'url'=>array('index')),
	array('label'=>'Create People', 'url'=>array('create')),
	array('label'=>'View People', 'url'=>array('view', 'id'=>$model->id)),
	array('label'=>'Manage People', 'url'=>array('admin')),
);
?>

<h1>Update People <?php echo $model->id; ?></h1>

<?php endif; ?>

<?php echo $this->renderPartial('_form', array('model'=>$model)); ?>

Здесь я думаю все понятно. Меню, крошки и заголовок отдаем только для обычного запроса. Для AJAX запроса — только форму.

И, наконец, изменим файл _form.php:


<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'people-form',
	'enableAjaxValidation'=>false,
)); ?>

	<p class="note">Поля помеченные <span class="required">*</span> обязательны для заполнения.</p>

	<?php echo $form->errorSummary($model); ?>

	<div class="row">
		<?php echo $form->labelEx($model,'name'); ?>
		<?php echo $form->textField($model,'name',array('size'=>60,'maxlength'=>255)); ?>
		<?php echo $form->error($model,'name'); ?>
	</div>

	<div class="row">
		<?php echo $form->labelEx($model,'surname'); ?>
		<?php echo $form->textField($model,'surname',array('size'=>60,'maxlength'=>255)); ?>
		<?php echo $form->error($model,'surname'); ?>
	</div>

	<?php if (!Yii::app()->request->isAjaxRequest): ?>
	<div class="row buttons ">
		<?php echo CHtml::submitButton($model->isNewRecord ? 'Создать' : 'Сохранить'); ?>
	</div>
	
	<?php else: ?>
	<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
		<div class="ui-dialog-buttonset">
		<?php
			$this->widget('zii.widgets.jui.CJuiButton', array(
				'name'=>'submit_'.rand(),
				'caption'=>$model->isNewRecord ? 'Создать' : 'Сохранить',
				'htmlOptions'=>array(
					'ajax' => array(
						'url'=>$model->isNewRecord ? $this->createUrl('create') : $this->createUrl('update', array('id'=>$model->id)),
						'type'=>'post',
						'data'=>'js:jQuery(this).parents("form").serialize()',
						'success'=>'function(r){
							if(r=="success"){
								window.location.reload();
							}
							else{
								$("#create").html(r).dialog("open"); return false;
							}
						}', 
					),
				),
			));
		?>
		</div>
	</div>
	<?php endif; ?>

<?php $this->endWidget(); ?>

</div><!-- form -->

Здесь изменения касаются лишь кнопки submit. Для обычного запроса оставляем все как было. Для AJAX запроса создаем CJuiButton.

Еще один маленький нюанс. Дело в том, что при каждом открытии диалогового окна, у нас каждый раз будет загружаться куча скриптов и css стилей, в которых может не быть необходимости, так как они могут быть уже загружены до этого. К счастью этого можно легко избежать, если воспользоваться расширением nlsclientscript.

И, напоследок, самое приятное. Все изменения описанные в этой статье вовсе не нужно вносить в ручную. Для этого достаточно изменить шаблоны кода Gii. О том как это сделать можно почитать здесь, а самые ленивые могут скачать переделанные шаблоны по этой ссылке на github.


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

Дмитрий
Ты молодец! Давно искал, но все статьи, которые читал не давали эффекта. Единственное, у меня почему то не получается переделать существующие круды типа создать страницу создать пользователя. Я копирую твой код, меняю peoples на page, user и т.д. А когда захожу на вид admin.php (список моих страниц), он постоянно пишет не определено свойство. Я уже не знаю как поступить
Ответить
Savvateev
Трудно что-то советовать не видя кода. Проверяй еще раз все внимательно. Наверное что-то упустил. Смотри какую ошибку выдает, ищи это место в коде, выясняй почему ругается.
Ответить
еще один Дмитрий
Классная статья! С условием скудной русскоязычной документации по yii. Делал по вашему примеру, не выводятся ошибки валидации в модальном окне. Без использования ajax ошибки выводятся.
Ответить
еще один Дмитрий
Заработало! В CActiveForm добавил параметр: enableClientValidation' => true Еще раз спасибо за статью)
Ответить
еще один Дмитрий
Как после завершения операции выводить флеш-сообщение?
Ответить
Roman
Добрый день! Спасибо за статью. Вопрос следующий. При вызове диалоговых окон на одной и той же странице, библиотеки js подгружаются заново. Как этого избежать?
Ответить
Savvateev
Читай внимательно предпоследний абзац.
Ответить
BeSimple
В форме CRUD _form ошибка на строке 47 ( $this->createUrl('update', array('id'=>$model->id)), надо так $this->createUrl('update', array('id'=>$model->tableSchema->primaryKey; ?>)), а то если первичный ключ не id a другой например id_cat то не работает update.
Ответить
paratrooper
Круто, как раз то что я искал! ЗЫ Счетчик показывает 3 года сайту. Поздравляю с круглой датой!
Ответить
Денискин
Всё круто! Спасибо за статью, но есть небольшая проблема с использованием nlsclientscript. Это расширение не грузит изображения прописанные в css файлах виджетов и jui. Firebug указывает на неправильный путь. Непонятно какой путь правильный. Как с этим бороться?
Ответить
Сергей
А это расширение не юзали? http://www.yiiframework.com/extension/x-editable/
Ответить
тоже Дмитрий
Привет. Подскажи в этом решении можно открыть несколько окон редактирования, потом в каждом по отдельности ajax'ом послать данные действию,грид обновляется ajax'ом,ну и соответсвенно каждое окно закрывается по отдельности?
Ответить
Александр
Спасибо за очень полезный материал! И архив с гитхаба вовсе не для ленивых, а для особо нетерпеливых :)
Ответить
Дмитрий
Здравствуйте! Все работает, но возникла проблема. При использовании этого рецепта перестали грузиться изображения. Использую расширение CImageHandler. Если убрать диалоговые окна, то изображения начинают грузиться. Подскажите как исправить. Заранее благодарю за ответ)
Ответить
это я
круть
Ответить
White Collar
Блин пол инета облазил два дня убил, огромое тебе спасибо ТС, радости нет придела
Ответить
Алексей
Очень круто! Спасибо!
Ответить
Мубин
Можете объяснить что сделает именно этот часть кода: $updateDialog =<<<'EOT' function() { var url = $(this).attr('href'); $.get(url, function(r){ $("#update").html(r).dialog("open"); }); return false;
Ответить
Savvateev
Это javascript код, который выполнится при нажатии кнопки редактировать, в виджете CGridView. Он открывает диалоговое окно с формой для редактирования.
Ответить
Мубин
Большое спасибо ...
Ответить
Мубин
Как сделать так чтобы при успешном сохранение данных, перезагрузилось не весь окно а, только виджет CGridView? if(r=="success"){ window.location.reload(); } else{ $("#create").html(r).dialog("open"); return false; }
Ответить
Savvateev
https://www.google.ru/search?q=pjax
Ответить
Мубин
Огромное спасибо. Отличная статья, супер. Очень помогли.
Ответить
Мубин
И ёще одна проблема, надеюсь вы мне поможете). Дело в том что, у меня в форме есть поля "Дата начало" и "Дата конец". Добавление и изменение выполняется вручную. Там, я использую виджет datetimepicker. http://www.yiiframework.com/extension/datetimepicker widget('CJuiDateTimePicker',array( 'model'=>$model, 'attribute'=>'date_begin', 'mode'=>'datetime' , 'options'=>array() )); ?> Не знаю почему внутри CJuiDialog это виджет не работает? В чем может быть причина?
Ответить
Savvateev
Трудно вот так сразу сказать в чем проблема. Надо разбираться. Помнится у одного человека были проблемы с подключением CKEditor. Тоже не работал внутри диалогового окна. Тогда мы это исправили. Вот ссылка http://savvateev.org/blog/58/. Может это как-то пригодится.
Ответить
Константин
Здравствуйте! Create и Update работают ЗАМЕЧАТЕЛЬНО! Правда к Create небольшое замечание - по хорошему после добавления новой записи грид должен позиционироваться на последнюю страницу, а иначе юзер не увидит добавленную запись, если он не на последней странице грида! БОЛЬШАЯ проблема с Delete при выключенном свойстве обновления CGridView через AJAX - Delete не работает! Можно как-то реализовать данную функцию, что-бы работала при отключенном, javascript и при включенном? У меня НЕ ПОЛУЧАЕТСЯ.... Получилось реализовать View, если есть необходимость могу выложить...
Ответить
Мубин
Сделал вот так: $('.button_add').click(function(){ $.ajax({ url: createUrl('create'); ?>, success: function(r){$('#create').html(r).dialog('open'); return false;} }); }); Неполучается(
Ответить


Оставить свой комментарий


Представтесь, пожалуйста *

Ваш комментарий

Число на картинке *

captcha

На хостинг