Periodically Updated Cgridview - Sorting Problem

Hi there, All,

I would like to ask for some help with the following issue:

On the main page of my app I have a CGridView widget updated every 5 seconds. For this purpose I use CController::renderPartial(). Here are some code snippets (for first, please look over the code within /* */ code blocks):

views/site/index.php:


<div id="active_device_grid">

<?php

	$this->renderPartial('activeDeviceGrid', array('model'=>$model, 'dataProvider'=>$dataProvider));

?>

</div>


<script type="text/javascript">

// On page load start periodically calling refresh

$(function () {


        /**********

        orderBy	= '';

        order	= '';	// 'asc' or 'desc'

        ***********/


	// Start refreshing the active device list

	refresh();

});


function refresh() {


	$.ajax({

		//type: 'POST',

		url: "<?php echo CController::createUrl('site/indexUpdate');?>",

		dataType: 'html',

		success:function(reply)

			{

				$('div#active_device_grid').html(reply);


				// Bind event handler to the just loaded html elements

                                /**********

				$("table.items th a").click(function() {

					clickHandler(this);

				});

                                **********/

			}

	});

	setTimeout(refresh, 5000);

	console.log("refresh called");

}

/********************

function clickHandler(element) {

	console.log('click event');


        // Little rude but ok.

	columnName = $(element)[0].innerText;		// e.g. Device name

	columnName = columnName.replace(' ','_');	// e.g. Device_name

	columnName = columnName.toLowerCase();		// e.g. device_name

	

	if(orderBy != columnName)

	{

		orderBy	= columnName;

		order	= 'asc';

	}

	else if(order == 'asc')

	{

		order = 'desc';

	}

	else

	{

		order = 'asc';

	}

}

********************/

</script>

views/site/activeDeviceGrid.php:


<?php

	$dataProvider = $model->searchActives();

	$dataProvider -> setPagination( array( 'pageSize' => 20, ) );


        /**********

        $dataProvider -> setSort( array(

			'defaultOrder'=>"$order_by $order",

	));

	**********/


	$this->widget('zii.widgets.grid.CGridView', array(

		'id'			=>'active-device-grid',

		'dataProvider'	=>$dataProvider,

		'filter'		=>$model,

		  

		'columns'=>array(

			'device_id',

			'device_name',

			'device_type',

			'device_firmware_version',

			array(

				'name'		=> 'equipment_name',

                                /**********

				'sortable'	=> true,

                                **********/

				'value'		=> array($model, 'getEquipmentNameFromDb'),

			),

		),

	));

?>

controllers/SiteController.php:


public function actionIndexUpdate()

{

	$model=new Device('search');

	$model->unsetAttributes();  // clear any default values

	if(isset($_GET['Device']))

	{

		$model->attributes=$_GET['Device'];

	}


        /**********

        $order_by = $_GET['orderBy'];

	$order = $_GET['order'];

        **********/


	$grid = $this->renderPartial(

                             'activeDeviceGrid', array(

                                    'model'=>$model,


                                    /**********

                                    'orderBy'=>$order_by,

                                    'order'=>$order,

                                    **********/

                             ),

                             true

        );

	echo $grid;

}

models/Device.php:


public function getEquipmentNameFromDb($data)

{

	// Get connected equipment from DB through equipment's id

	$eq_id = $data->equipment_id;

	$query = "SELECT equipment_name FROM equipment WHERE equipment_id=$eq_id";

		

	$eq_name = Yii::app()->db->createCommand($query)->queryScalar();

	if( $eq_name )

	{

		return $eq_name;

	}

	else	// No equipment connected to device

	{

		return '';

	}

}


public function searchActives()

{

	$criteria=new CDbCriteria;


	$criteria->compare('device_id',$this->device_id,true);

	$criteria->compare('device_name',$this->device_name,true);

	$criteria->compare('device_type',$this->device_type,true);

	$criteria->compare('device_active', 1);			// !!!

	$criteria->compare('device_firmware_version',$this->device_firmware_version,true);

	$criteria->compare('device_available_storage',$this->device_available_storage,true);

	$criteria->compare('equipment_id',$this->equipment_id,true);

	$criteria->compare('device_created',$this->device_created,true);


	return new CActiveDataProvider($this, array(

		'criteria'=>$criteria,

		'sort'=>array(

			'defaultOrder'=>'device_id ASC',

		),

	));

}

So an external application keeps the database up to date managing active/inactive devices (with device_active set to 1 or 0 in the data table.

It works fine, with two annoying little problems.

  • First, if I sort the grid data by any of the columns, the renderPartial() method called by the refresh() javascript function, of course, resets the sorting.

  • Second, I cannot make the grid be sortable by the calculated column ‘equipment_name’.

However I made some efforts as you can see taking a look on lines within the code block. That would be, in the main view in the javascript part, keep tracking the user interaction with the grid and having the actual state in two variables orderBy (column name) and order (ascending or descending), and pass it towards the partially rendered view (activeDeviceGrid.php). With this I have got two other problems :)

The view activeDeviceGrid.php said "Undefined variable: order_by", what can be in connection with this article: link, but I cannot figure out the solution.

Secondly the click event handler in index.php gets invoked only once after each refresh() call.

For now I’ve got a bit confused about all this , but I hope this post is clear enough.

I would be very thankful for an answer of any issue around this subject or another cleaner way to do all that I want.

Best regards,

Klorti

[color="#006400"]/* Moved from "Bug Discussions" to "General Discussion for Yii 1.1.x" */[/color]

Hi Klorti, welcome to the forum.

I did something similar sometimes ago.

What I did was to use “$.fn.yiiGridView.update()” javascript function of CGridView to update the grid content. It’s convenient because it will take care of the submission of the sorting and filtering parameters for you.

views/site/index.php:


<div id="active_device_grid">

<?php

	$this->renderPartial('activeDeviceGrid', array('model'=>$model, 'dataProvider'=>$dataProvider));

?>

</div>


<?php

Yii::app()->clientScript->registerScript('grid-update', "

var timer = setTimeout(refresh, 10000);

function startRefresh(id, data) {

	clearTimeout(timer);

	timer = setTimeout(refresh, 10000);

}

function refresh(){

	$.fn.yiiGridView.update('active-device-grid');

}

/* cancel timer on clicks on sorters on the header */

$('#active-device-grid').on('click', 'th a', function(){

	clearTimeout(timer);

});

");

?>



views/site/activeDeviceGrid.php:




<?php

	$dataProvider = $model->searchActives();

	$dataProvider -> setPagination( array( 'pageSize' => 20, ) );

	$this->widget('zii.widgets.grid.CGridView', array(

		'id' =>'active-device-grid',

		'dataProvider' => $dataProvider,

		'filter' => $model,

                'afterAjaxUpdate' => 'startRefresh', /* important */

		  

		'columns'=>array(

			'device_id',

			'device_name',

			'device_type',

			'device_firmware_version',

			array(

				'name'		=> 'equipment_name',

				'sortable'	=> true,

				'value'		=> array($model, 'getEquipmentNameFromDb'),

			),

		),

	));

?>



controllers/SiteController.php:




public function actionIndex()

{

	$model=new Device('search');

	$model->unsetAttributes();  // clear any default values

	if(isset($_GET['Device']))

	{

		$model->attributes=$_GET['Device'];

	}

        if (Yii::app()->request->isAjaxRequest)

	        $this->renderPartial('activeDeviceGrid', array('model'=>$model));

        else

                $this->render("index", array("model" => $model));

}



models/Device.php:




public function searchActives()

{

	$criteria=new CDbCriteria;


	$criteria->compare('device_id',$this->device_id,true);

	$criteria->compare('device_name',$this->device_name,true);

	$criteria->compare('device_type',$this->device_type,true);

	$criteria->compare('device_active', 1);			// !!!

	$criteria->compare('device_firmware_version',$this->device_firmware_version,true);

	$criteria->compare('device_available_storage',$this->device_available_storage,true);

	$criteria->compare('equipment_id',$this->equipment_id,true);

	$criteria->compare('device_created',$this->device_created,true);


	return new CActiveDataProvider($this, array(

		'criteria'=>$criteria,

		'sort'=>array(

			'defaultOrder'=>'device_id ASC',

		),

	));

}



Not examined carefully, so it might have some errors. But I hope you understand what I mean. :)

Some notes:

  • I used ‘afterAjaxUpdate’ of the grid to restart the updating timer.

  • I used jQuery.on() to handle the click event. It’s very convenient.

  • I used actionIndex to handle ajax call.

About the sorting by "equipment_name", you might be better consider using the relational AR.

Probably you can establish "Device BELONGS_TO Equipment" relation or "Device HAS_ONE Equipment" relation. Then you can access the name of the equipment as "$device->equipment->name".

And, read the following wiki:

http://www.yiiframework.com/wiki/281/searching-and-sorting-by-related-model-in-cgridview

Wow, thanks! Very short reaction time :) Right now I can’t treat with this question but asap I will try your suggestions.

I could make both solutions work properly.

Thank you!