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:
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.
'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.
$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:
'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.
'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.
Total 13 comments
Hi,
I was having a javascript error : "a.curCSS is not a function".
Found why : The version of jquery ui used in this wiki is outdated regarding the jquery version provided in fresh yii install. I recomand using ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.min.js, worked for me.
Great wiki though, worked perfectly for me thank you!
It works like charm. FYI, for guys like me who know little about jQuery UI, there is nothing to download or install. Just follow the this post and pay special attention to the filter part in the controller if you have one. For me, I was getting the Not Found error because the filter I set up in my controller applied to all actions which including sort action. Nice post!!
it works. thanks
Thank you blindmoe for this great article. It helped me a lot.
And I'd like to share my experience with you all. I think I have come up with a solution for the ajax update problem.
You can make use of the CGridView::afterAjaxUpdate to make the grid sortable again after it is updated by ajax. (The situation can occur when you have applied a filter, deleted an item, moved to another page by pagination, or changed the item count in the page.)
First, enclose the installation code of sortable into a function, and call it from $(document).ready() to make the grid sortable for the initial loading of the page.
Instead of:
Write like this:
And call the installation function after the grid has been updated by ajax ... this will be done via CGridView::afterAjaxUpdate.
And, at last, you have to modify the sort action in the controller to give the items the correct sort order numbers when they are filtered or paginated. You should not simply give them a series of number from 0 to n-1. They can collide with the sort order numbers that other items not in the current page might have.
First, retrieve all the sort order numbers that the items in the current page have, then re-assign them to the listed items.
The CSRF token and it's name can be retrieved from CHttpRequest. There's no need to wrap the grid in a form. You can write like this ...
Great wiki blindmoe. Just wondered if anyone has come up with a solution yet to keep the sortable function working after changing the page or filtering (as mentioned by SSaarik in the previous post)? My jQuery knowledge is minimal and I just don't know where to begin.
In the meantime, I've read that disabling ajax updates on the grid can help. Add the following line to the CGridView configuration array in your View...
How can I make this work with filters enabled? After filtering sortable function drops.
Trying to figure it out, I'll give solution if I find one.
Thanks! I'll try this out right away.
@skeef This is a common problem when you enable CSRF validation and use AJAX in your pages. To fix it in this case wrap the grid in form tag (to be sure the CSRF token is present at least once in the page):
In your javascript file where you do the ajax call you need to send the CSRF token as well:
You could try changing the code written above to:
Thank you for yuor work. All told accessible and understandable.
But I have some Error.
If
in FirePHP I see an Error:
The CSRF token could not be verified
Prompt solution, please
Allain, I think you are actually correct, but it is a good practice to have validation rules for all of the properties in your models. I will switch the verbiage to specify that #2 is optional.
Can you explain this statement, since sortOrder isn't being provided by the user and isn't being loaded using $project->attributes, I'm not sure this is necessary.
I've incorporated this into my project and confirmed that all works fine without it.
Thank you for this, saved me a lot of blind guessing.
Leave a comment
Please login to leave your comment.