CGridView + partial rendering = filter onchange bug

Hi guys,

i guess i’ve found a bug coming up in similar situations like the following, would be useful if someone could confirm.

In a view ("index"), i build a simple menu what loads an another view ("_browse") by ajax through the action "browse", displaying a CGridView listing the records in my models.


foreach ($this->module->models as $m) {

	echo CHtml::ajaxLink($m, $this->createUrl('default/browse', array(

			'_modelname' => $m

		)), array(

			'update' => '#browsecontainer'

		)

	) . " ";

}


?>


<div id="browsecontainer">

	<% $this->renderPartial('_browse'); %>

</div>

Here is the "browse" action in my controller:


public function actionBrowse() {


	if (@$_REQUEST['_modelname']) {

	

		$this->modelname = $_REQUEST['_modelname'];

	

		$model = new $this->modelname("search");

		$model->unsetAttributes();

	

		if (@$_REQUEST[$this->modelname])

			$model->attributes = $_REQUEST[$this->modelname];

	

		$this->renderPartial('_browse', array(

			'modelname'=>$this->modelname,

			'model'=>$model,

			'rows'=>$model->search(),

			'columns'=>array_merge(

				array_keys($model->getTableSchema()->columns),

				array(

						array(

								'class' => 'CButtonColumn'

						)

				)

			)

		),false,true);

	}

	Yii::app()->end();

}

In the view "_browse", there is a CGridView instance, using the variables filled in the "browse" action:


<?php 

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

  'id' => 'mymodelbrowser_' . $modelname,

  'filter' => $model,

  'dataProvider' => $rows,

  'columns' => $columns,

  'enablePagination' => true

));

?>

The issue is, when i click an ajaxLink in the "index" view named eg. "Entity", then click an another model link eg. "Feature" then clicking back to the "Entity", the javascript onchange handler seems like called twice and two GET will be sent when using a filter field above a column (Firebug used). Navigating away again from the "Entity" GridView and back again, the filtering GET will be send already 3 times, away and back again results 4 GET/one filtering etc etc. However the filtering works, it should be a bug, i guess the CGridView widget is not properly cleaned up/reseted on the client side when rendering again.

Regards

nlac

Hi!

A couple of things - first of all, I’m not sure how your example works at all (presumably you have omitted some code in what you have posted), since in your index view you partially render the _browse view, without passing any variables to it, when this view refers to specific variables - I would have thought that your index would generate a PHP error!

That aside, I think you’ve found the eternal problem of using ajax updates and trying to update javascript in those updates.

I don’t pretend to be an authority, but as far as I am aware your gridview shouldn’t be able to do any ajax at all, since it is generated for the first time in an ajax request, and as such, its javascript dependencies are not included on the main page (unless of course you’re using a gridview on the main index page too).

When you first load a gridview, it registers its javascript file (yiiGridView.jquery.js I think) on the page, and adds an inline script to the bottom of the page to register that particular gridview (with the div it should be updating, and options etc). The javascript hooks are then registered when the page loads.

The gridview can update itself through ajax because it loads the same route it was created on, with the appropriate query parameters, then extracts the div that it was defined to replace from the html for the entire page, and replaces the div that exists on the current page with it.

Obviously, if you make an ajax update that replaces the gridview (rather than the gridview replacing its contents), then you remove the gridview’s Html, but not its registered javascript hooks, and you don’t register the hooks for the new gridview, resulting in an incoherence between your Html content and your javascript hooks.

You would need to look more closely at the gridview javascript file to confirm all this (things on the server side are simple with just the registerClientScript method being concerned in the CGridView class).

So to sum up, I don’t think you’re looking at a Yii bug, but rather a behaviour that is caused by using the gridview in a way that it hasn’t (yet) been designed for.

To do what you require, I would suggest looking closely at the gridview javascript file and at the inline script that the gridview registers, and modifying your original ajax link for each of your models, so that when clicked, they don’t just ajax load the gridview Html, but also de-register the previous gridview’s existing javascript hooks, and register the hooks for the new one (by simulating the inline script that gridview generates).

Hope this helps.

Hi RedRabbit,

thanks for the reply. Sorry, my code posted here was indeed not a working one, i simplified it too much when copied here. Fortunately i could fix the problem (i think you suggested the same), calling this function before update the grid:




beforeRenderGridView = function(id_gw /*id of the gridview*/) {


	if ($.fn.yiiGridView) {


		for(var id in $.fn.yiiGridView.settings) 

			if (!id_gw || id_gw==id) {


				var st = $.fn.yiiGridView.settings[id];

				if (st.inputSelector)

						$("body").undelegate(st.inputSelector, "change");

				if (st.updateSelector)

						$(st.updateSelector).die("click");

		}


	}

}



Indeed, the problem is jQuery possibly stores that event handlers in hash tables with the selector as key so those bindings remain even after the dom objects belonging to the selector are destroyed/regenerated.

I have to mention that without doing this cleanup, the sorting causes the same effect : one more extra request/sort after every partial update.

Regards and thanks for looking into it,

nlac