[Solved] Recursive ajax call Problem

Hi

I just run into a recursive call problem while making an ajax component. Looks like others are also having similar problems. The single solution, I found till date the ZController by Zaccaria , ended up doing the same thing.

The code

Controllers >>

AjaxcomponentsController




<?php


class AjaxcomponentsController extends Controller

{


	public function actionAjaxpartcomponent($id)

	{

            $id.='10';

		$this->renderPartial('ajaxpartcomponent',

                        array('ajaxpartcomponentdata'=>$id),FALSE,TRUE

                        

                        

                        );

                

	}

}



SiteController




class SiteController extends Controller

{

        public function  actionAjaxpage()

        {

            $this->render('ajaxpage',array('data'=>1));

        }

}



[b]Views >>

site/ajaxpage.php[/b]




<h2>Ajax Test Page</h2>

<p>Test page for the ajax testing of components system.</p>


<br/><hr/>

<div id="ajaxpartcomponent">

    <?php $this->renderPartial('/ajaxcomponents/ajaxpartcomponent',array('ajaxpartcomponentdata'=>$data));  ?>

</div>

<br/><hr/><br/>

<?php echo CHtml::textField('texttextfield'); ?>


Just completed the ajax component render.




ajaxcomponents/ajaxpartcomponent.php





    <?php

        echo $ajaxpartcomponentdata;

        echo CHtml::ajaxButton('Append 10',

                Yii::app()->createUrl('/ajaxcomponents/ajaxpartcomponent/',array('id'=>$ajaxpartcomponentdata)),

                array('update'=>'#ajaxpartcomponent'),

                array('id'=>'ajaxpartbutton')

                );

    ?>




The Problem

when the ‘Append 10’ is clicked the first time a single request is ajaxcomponents/ajaxpartcomponent/1?_xxxxxxxxxxxxx is made.where xxxxxxxxxxxxx is a 13 digit number.

when clicked the second time the requests are

[indent]

ajaxcomponents/ajaxpartcompnent/1?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/110?_xxxxxxxxxxxxx

[/indent]

on third time the requests are

[indent]

ajaxcomponents/ajaxpartcompnent/1?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/110?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/11010?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/110?_xxxxxxxxxxxxx

[/indent]

on fourth time the requests are

[indent]

ajaxcomponents/ajaxpartcompnent/1?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/110?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/11010?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/110?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/11010?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/1101010?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/11010?_xxxxxxxxxxxxx

ajaxcomponents/ajaxpartcompnent/110?_xxxxxxxxxxxxx

[/indent]

and so on, on subsequent requests

The requests are shown in the exact order they are shown by firebug

Using Zcontroller

when using ZController the button do not work at all, and it gives out the same results as without using ZController when I change the renderPartial($view,$data=null,true) in ZController to renderPartial($view,$data=null,true,true)

Am i missing something

Regards

Amit Singh

I have no idea if this is what is happening, but I think it’s possible that the problem is that the ajax function is being registered each time you hit the renderPartial in the ajaxaction.

So you’re adding a new click listener each time and each one is activating when the click happens. I think.

So, you start with the following auto-generated ajax code added to the bottom of your page:




$('#ajaxpartbutton').click(/*update code is here*/);



each time you run the partial, I bet it’s adding another, so you end up with:




$('#ajaxpartbutton').click(/*update code is here*/);

$('#ajaxpartbutton').click(/*update code is here*/);

$('#ajaxpartbutton').click(/*update code is here*/);

$('#ajaxpartbutton').click(/*update code is here*/);



If you look at the page source via firebug, it might be very revealing.

Why do you processOutput?

http://www.yiiframework.com/doc/api/1.1/CController#processOutput-detail

Just do this:

$this->renderPartial(‘ajaxpartcomponent’,

                    array('ajaxpartcomponentdata'=&gt;&#036;id));

It looks to me that is redirected… never had that problem but I never use processOutput and I do use extensive use of AJAX

Hi Dana

Its not just adding up another clickevent, I am getting 2[sup]n-1[/sup] ajax requests, but should have just n number of requests as in the case specified by you. ( n is the number of time the button is clicked)

Firebug is showing just the first script, although it calls up the subsequent scripts.

Thanks.

Hi Antonio

$this->renderPartial(‘ajaxpartcomponent’,

                    array('ajaxpartcomponentdata'=&gt;&#036;id));

actually makes no change to the script on rerendering of the subview. Although it calls the script just one time over and over again on each click but the data it passes to the controller/action remains the same as was the first time. I am just trying to change this and call the controller/action using the different value each time.

Exponential growth is what I’d expect. First time you load page you have one click listener. Aftet first click you have 2 listeners. Next click you have both fire and each adds 1 so you have 4 listeners each at a different stage of progression (+10s) next click 8 more are created etc etc

I think Dana is right,

You should do it differently, you should write a script that handles AJAX calls on the main layout, for example:




$('.ajaxbuttonsonmypage').live('click',function(e){

//handle ajax calls here

// we could even read meta data from the button so to know the URL to call <img src='http://www.yiiframework.com/forum/public/style_emoticons/default/smile.gif' class='bbc_emoticon' alt=':)' />

var url = $(this).attr('url');

e.preventDefault();

});






then when you render the button you do:




echo CHtml::button('click me to ajax', array('type'='button','class'='ajaxbuttononmypage', 'url'=>'http://url'));



when you render the button via ajax, the main layout script will know if the document has changed, look for the classes and bind the click events :)

This is the perfect scenario for the jquery.livebuttons plugin

http://www.ramirezcobos.com/2010/12/15/jquery-livebuttons-plugin/

test it and tell me what do you think

cheers

Hi,

it looks like, i have the same situation/problem, posted here:

http://www.yiiframework.com/forum/index.php?/topic/10703-loading-clistview-by-ajax-the-clistview-js-and-css-is-missing-and-pagination-links-load-the-partial/page__view__findpost__p__70844

That’s the same situation, right?!

…but there is NO exponential growth.

I’m not sure, if the posted solution “jquery.livebuttons” fits to “my” problem.

By the way: is this behavior an bug or unfortunate implementation of ajax in yii? Or may i have an insufficient application design or an insufficient understanding of ajax and yii??

-regards-

hi rall0r if your problem is in clistwiev i have solution for that , best way is to use jquery delegate()

hire is my working example witch works for me




$url = $this->createAbsoluteUrl('contoller/action');

$script = "jQuery(function($) {

jQuery('body').delegate('.getid', 'click', function(){

$('#error').ajaxError(function(event, request, settings){

$(this).append('Error requesting page ' + settings.url);

});

var ids = jQuery(this).attr('id');

jQuery.ajax({

type: 'GET',

url: '".$url."',

data: 'id='+ids,

beforeSend: function(){jQuery('#pregled').hide();jQuery('#detalji').addClass('webloading');jQuery('#detalji').append('<div id=\"webtext\">Ažuriranje</div>');},

success: function(data, textStatus, XMLHttpRequest){jQuery('#detalji').removeClass('webloading');jQuery('#webtext').remove();jQuery('#detalji').html(data);}

});

});

});";

Yii::app()->clientScript->registerScript(1,$script,CClientScript::POS_END);


echo '<div id="detalji"></div>';

echo '<div id="error" style="color:red;"></div>';

echo '<div id="pregled" class="pregledoglasa">';

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

'dataProvider'=>$data,

'itemView'=>'items', 

'cssFile'=>Yii::app()->request->baseUrl.'/css/clistview.css',

'sortableAttributes'=>array(

'naslov'=>'Naslovu',

'zupanija'=>'Županiji',

'mjesto'=>'Mjestu',

'pobroj'=>'Poštanskom broju',

),

));

echo '</div>';






in items.php view




<?php 

CHtml::image('image/arrow.png','Detalji',array('id'=>$data->id,'class'=>'getid')


?>




And in detail view of item:

foreach CJuiTabs tab you can add ajax call


$tabWidget=$this->widget('zii.widgets.jui.CJuiTabs', array(

'tabs'=>array(

'Tab'=>array('ajax'=>$ajaxUrl),

'Tab 2'=>array('ajax'=>$ajaxUrl),

),

'options'=>array(

        'collapsible'=>true,

    ),




);

I am sorry for not being able to help you more but again, I do use livequery plugin in my projects, which I developed facing your problems as I do render forms-content via AJAX and I didnt want to have not to rewrite more functions for every class or specific button in my programing. If you look at its code, you will realize that what I do is a hack of the .live() jquery function. If not livequery, use the .live() example as I showed you or the one exposed with .delegate by Igor which is also good, I am 100% sure it will work because I develop commercial products with this technique.

My CMSs designs have two pages: one login, and one main, the rest are all controlled via AJAX calls, user stays in one main page.

This is not a problem of Yii, this framework allows us to automate AJAX calls but we have to design our CMS properly in order to avoid problems like that. If you wish to use AJAX buttons as your code shows above, then I recommend you to not re-render that content again in your panel. Your problem seems to me that you render AJAX content with buttons scripts in it. Why dont you try what I told you (no livebuttons) or what Igor says and afterwards you get your conclusions on this.

About your last question, do not worry, I think we all went and go through the same questions all the time. Once you find a solution please posted, I will like to see what it was.

Cheers

What else to say, Respect :)

Hi

Just solved the problem. may be my approach is a little wrong but it works.

Here is what i did.

The CHtml::ajaxbutton calls CHtml::button which in turn calls CHtml::clientChange

Now the clientChange has a third argument $live=TRUE, This argument is not provided by button function and nor is there any option for the same in ajaxButton hence it is just used as true.

when this $live is true it generated the ajax script using delegate


jQuery('body').delegate('#apcbtnappend','click',function(){ ...... }

which actually causes the event to be attached to current as well as future elements, thus re-rendering of the div has no effect on the attached event and it can go ahead on the new button control. thus making a new and previously active event attached to the same control.

When $live is false the ajax script is generated using the event binding. as


jQuery('#buttonid').click(function(){ .......  }

now this one expires as soon as the button is re-rendered.

so I actually did is to set the $live = false so as to generate even binding ajax script.

Ways to Control $live

  1. set default argument from $live=TRUE to $live=FALSE

[indent]Simple easy and fast, as given in post http://www.yiiframework.com/forum/index.php?/topic/1…18.

Problem: We don’t control $live, rather we set it as False, I don’t know all the functions that call it and neither do i know why it is set as there, further i can never use this to delegate as it will always be false. So its no way to control. cant understand why they do so as mentioned in the given post.

[/indent]

  1. write your own ajaxButton()

[indent] We may need the original ajax button so lets say we write the ajaxButtonNonLive() which in turn call buttoNonLive() which calls the clientChange() with third argument as false. Nice easy and we have the control.

Problem: There is not just ajaxButton but ajaxLink and ajaxSubmitButton too, so we have do the same for these too.

[/indent]

  1. Control Dynamically using $ajaxOptions ( I use this )

[indent]

we have the $ajaxOptions for all the three and it is used in clientChange(), so I just added a small code in the clientChange just before it checks the $live, to pass the value of ‘useLive’ to it. Now useLive can be defined in ajaxOptions and can be used to turn $live TRUE or FALSE.

CHtml::clientChange Code




                /* Add this code  -- Begin -- */

                if(isset ($htmlOptions['ajax']['useLive']))

                    $live = $htmlOptions['ajax']['useLive'];

                /* -- End -- */


                // Old Code continue

		if($live)

			$cs->registerScript('Yii.CHtml.#'.$id,"jQuery('body').delegate('#$id','$event',function(){{$handler}});");

		else

			$cs->registerScript('Yii.CHtml.#'.$id,"jQuery('#$id').$event(function(){{$handler}});");

		unset($htmlOptions['params'],$htmlOptions['submit'],$htmlOptions['ajax'],$htmlOptions['confirm'],$htmlOptions['return'],$htmlOptions['csrf']);




You will find this at the end of the function.

Now in view used as




        echo CHtml::ajaxButton('Append 10',

                Yii::app()->createUrl('/ajaxcomponents/ajaxpartcomponent/',array('id'=>$ajaxpartcomponentdata)),

                array('update'=>'#ajaxpartcomponent','useLive'=>FALSE),

                array('id'=>'apcbtnappend'));



[/indent]

It’s your choice as how you want to solve this problem.

Hope this Helps

Amit Singh

I had this problem lot of time ago, and I compressed my solition in this extesion.

I don’t remember what is the theory behind it, but for me works… :D

Congratulations Amit!

It is good information and better explained that I did. See? At the end is about .live or .delegate :) or event binding problems

You should write a wiki article my friend so to help others with that. Good info, thumbs up and positive vote from me.

Thanks for sharing!

Cheers

update:

Nevertheless, we should avoid writing directly to Yii classes, how can that be solved?

I used ZController initially but unfortunately it did’t solve the problem.

I will do that, as soon as I get some time, but first I have to find out where turning the $live to FALSE causes a problem.

To approach the clientchange $live = false,As i see it`s only possible by

calling beginWidget (Class, $properties=array ( )), you can define property,

please correct me if i am wrong.




 $this->beginWidget('CActiveForm', array(

    'id'=>'user-form',

    'enableAjaxValidation'=>true,

    'clientChange'=>array(

    'click',

    array(),//$htmlOptions

    'live'=>false,

    )

)); 




Antonio and Igor: many thanks for the reply

Only today I had the opportunity to read the posts intensive.

Ajax is completely new for me and I often have problems to follow you.

In summary, both solutions go to overlay the 'Click ’ event. (?)

Respectively the yii default functions for "clicks" right?

I will try to apply the suggestions on my application.

It will take a few tries before I will be able to use the examples. :wink:

Thank you

//Edit:

I’m at an very frustrating point, as no solution works.

Probably i’ve got a lack of background knowledge to understand the instructions written here! :frowning:

First I tried the solution of Igor, because it looked easy and simple.

The Ajax request renders a view that I have extended by the lines from Igor:


<?php

$script = "jQuery(function($) {

jQuery('body').delegate('.getid', 'click', function(){

$('#error').ajaxError(function(event, request, settings){

$(this).append('Error requesting page ' + settings.url);

});

var ids = jQuery(this).attr('id');

jQuery.ajax({

type: 'GET',

url: '".$url."',

data: 'id='+ids,

success: function(data, textStatus, XMLHttpRequest){jQuery('#detalji').removeClass('webloading');jQuery('#webtext').remove();jQuery('#detalji').html(data);}

});

});

});";


echo '<div id="detalji"></div>';


Yii::app()->clientScript->registerScript(1,$script,CClientScript::POS_END);




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

'dataProvider'=>$dataProvider,

'itemView'=>'_view',

'ajaxUpdate'=>false

)); ?>

However, no effect. I click a Row CListView in Ajax Tab 1 Get Requst is triggered. I switch from Ajax tab-> Static tab-> Ajax tab and click again the same CListView Row, there are 2 Ajax Request.

In addition, for each click gelasden also a CListView from the static tab!?

  1. Trial: $live

In what widgets (or where) I can use $live?


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

'dataProvider'=>$dataProvider,

'itemView'=>'_view',

'ajaxUpdate'=>false,

'clientChange'=>array(

    'click',

    array(),//$htmlOptions

    'live'=>false)

));



does not work.

I feel just absolutely disoriented …

:frowning:

Hi Amrit,

I see you changed CHtml::clientChange to look at ajaxOptions.

This isnt necessary though. If you look at ajaxButton/ajaxSubmitButton/ajaxLink, they all have the $htmlOptions argument.

If you trace the API, the $htmlOptions argument is passed to self::clientChange(‘click’,$htmlOptions).

And if you look at clientChange:

http://www.yiiframework.com/doc/api/1.1/CHtml#clientChange-detail

So htmlOptions has the following option:

  • live: boolean, whether the event handler should be attached with live/delegate or direct style. If not set, liveEvents will be used. This option has been available since version 1.1.6.

And if you look at liveEvents:

http://www.yiiframework.com/doc/api/1.1/CHtml#liveEvents-detail

So you can just do something like:


echo CHtml::ajaxSubmitButton('Submit', 

	      			CHtml::normalizeUrl(array('/controller/view','id'=>$model->id, 'ajax'=>true)), 

	        		array('success'=>'js:function(data){alert(data);}'), 

	        		array('id'=>'idOfThisButton', 'live'=>false));

Thank you! Much easier than changing Yii backend code.