Creating a jQueryUI Sortable CGridView

I have had to do this a couple of times now so I figured I would share it with the community. I am going to keep this short because I really hope that you are familiar with jQueryUI's Sortable class before starting this tutorial.

Here are the basic steps to achieve this:

  1. Make sure your database table has a 'sortOrder' field.
  2. (Optional) Add the 'sortOrder' field to your Rules() function in your model
  3. Add the 'actionSort()' method to your controller to apply the sorting via ajax
  4. Add jQuery UI to your view that has the CGridView
  5. Setup the jQuery UI Code to work with the CGridView in question
  6. (Optional) Apply the sorting to your model's search() function if need be

So lets get started!

Step 1 is self-explanatory, just make sure that you have an INT field in your database to store the sortOrder of each item ( for this article we will call this field 'sortOrder' )

Step 2: This step is optional but recommended. Add this line to the model who's items you are sorting.

array('sortOrder', 'numerical', 'integerOnly'=>true),

Step 3: This is the part where we add the code to a controller that will apply the new sorting order to the rows in your database. Note I will be using a controller titled 'Project'. You will see me link to this controller in the jQueryUI Sortable javascript code.

  • Note: Please make sure that the user who will be doing the sorting has access to this action in your 'accessRules()' for the controller in question.

'Project' is the model that I am applying the ordering to. You can add a CDbCriteria in here if you need to sort just specific rows.

public function actionSort()
{
	if (isset($_POST['items']) && is_array($_POST['items'])) {
		$i = 0;
		foreach ($_POST['items'] as $item) {
			$project = Project::model()->findByPk($item);
			$project->sortOrder = $i;
			$project->save();
			$i++;
		}
	}
}

Step 4: OK Here is the fun part. We need to setup jQuery UI to link to the CGridView w/o having to modify the source of the CGridView in any way. By doing this we don't have to modify any of the core files or extend CGridView in any way.

<?php
	$str_js = "
		var fixHelper = function(e, ui) {
			ui.children().each(function() {
				$(this).width($(this).width());
			});
			return ui;
		};
				
		$('#project-grid table.items tbody').sortable({
			forcePlaceholderSize: true,
			forceHelperSize: true,
			items: 'tr',
			update : function () {
				serial = $('#project-grid table.items tbody').sortable('serialize', {key: 'items[]', attribute: 'class'});
				$.ajax({
					'url': '" . $this->createUrl('//project/sort') . "',
					'type': 'post',
					'data': serial,
					'success': function(data){
					},
					'error': function(request, status, error){
						alert('We are unable to set the sort order at this time.  Please try again in a few minutes.');
					}
				});
			},
			helper: fixHelper
		}).disableSelection();
	";

	Yii::app()->clientScript->registerScript('sortable-project', $str_js);
?>
<?php $this->widget('zii.widgets.grid.CGridView', array(
	'id'=>'project-grid',
	'dataProvider'=>$model->search(),
	'filter'=>$model,
	'rowCssClassExpression'=>'"items[]_{$data->id}"',
	'columns'=>array(
		'id',
		'title',
		'categoryId',
		'sortOrder',
		array(
			'class'=>'CButtonColumn',
		),
	),
)); ?>

OK So first off we are setting up the jQueryUI Sortable object in javascript and attaching it to our CGridView. Things to note are: 'project-grid' is the 'id' of our CGridView and will need to be switched to the ID of your CGridView. Also, the following line will need to be altered to point to your //controller/action path where you added the 'actionSort()' function: ~~~ [javascript] 'url': '" . $this->createUrl('//project/sort') . "', ~~~

Once the javascript is setup we need to setup the CGridView. The only two things out of the ordinary are that we are explicitly setting the 'id' of the CGridView and we are also setting the 'rowCssClassExpression' variable to work with jQueryUI Sortable. ~~~ [javascript] 'id'=>'project-grid', 'rowCssClassExpression'=>'"items[]_{$data->id}"', ~~~

Step 5: In your view file or controller that is displaying the CGridView you will need to tell Yii to add jQueryUI to your page.

Yii::app()->clientScript->registerScriptFile('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js');

Step 6: If you are planning on using your model::Search() function to setup DataProviders for things like CListView and CGridView I recommend you add the following line to the Search() function towards the bottom before the CActiveDataProvider is created:

$criteria->order = 'sortOrder ASC';

I hope this helps some people out! If you are having any problems please go through this write-up again to make sure you didn't miss anything.