ajaxButton, ajaxLink and links with confirm param

I was trying such an example:

In standard webapp from yiic i've changed in view/site/index.php and the begining of the file



<div id="target">


<?php


	$df = new CDateFormatter('en');


	echo $df->formatDateTime(time());


?>


<br />


<?php echo CHtml::ajaxLink("TestLink",


		array('site/updateTarget'),


		array('replace'=>'#target'),


		array('name'=>'testLink', 'confirm' => 'Are you sure?')


	); ?>


<br />


<?php echo CHtml::link("TestLink2",'#',


		array('name'=>'testLink2', 'confirm' => 'Are you sure?')


	);


?>





and at the end of file



</div>


and in SiteController.php



public function ActionUpdateTarget() {


	$this->renderPartial('index');


}


First click of TestLink works fine.

But after this neither TestLink nor TestLink2 will work, because "outside" javascript loaded on document ready will not affect these links. Neither ajax will work nor confirm.

Maybe all links which can use javascript for confirmation, ajax or something else should have a parameter such as 'scriptPosition' => 'onclick' ?

This is mainly because the ajax response replaces the whole "target" including the links which have exactly the same ID. Try replacing "testLink" with a different ID in your ajax view and you should see it works as expected. I can't seem to find a way to solve this issue (a link is replaced with another link with the same ID).

Changing id will not work.

My idea to solve this problem:

It could be done by passing parameter such a scriptPosition as an additional parameter (or even in htmlOptions to  CHmtl::clientChange()

and in last line of CHmtl::clientChange()



<?php


if($htmlOptions['scriptPosition'] === CClientScript::POS_TAG) {


  $htmlOptions['on'.$event] = $handler;


} else {


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


}


unset($htmlOptions['scriptPosition']);


But I haven't checked this yet

then only



<?php echo CHtml::ajaxLink("TestLink",


      array('site/updateTarget'),


      array('replace'=>'#target'),


      array('name'=>'testLink',


            'confirm' => 'Are you sure?',


            'scriptPosition' =>CClientScript::POS_TAG)


   ); ?>

I actually tried this, but still doesn't work.

It works for me … Maybe previos didn't work because of not defined CClientScript::POS_TAG

in view:



<?php echo CHtml::ajaxLink("TestLink",


      array('site/updateTarget'),


      array('replace'=>'#target'),


      array('name'=>'testLink', 'confirm' => 'Are you sure?', 'scriptPosition' => 1)


   ); ?>


in CHtml::clientChange



			if(isset($htmlOptions['scriptPosition'])) {


			  $htmlOptions['on'.$event] = $handler;


			} else {


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


			}


			unset($htmlOptions['scriptPosition']);


Probably there should be performed  some additional javascript escaping, but it works.

Could you please create a ticket for this? Thanks.

Done

The same thing aplies to other widgets which are not using events to work such as CTreeView.

CTreeView, CTabView, etc. will not work when it will be loaded by ajax from renderPartial() method. Should I create a ticket for it?

I've reopened ticket #38 to track this issue. I think we need a better solution.

Not sure, if this fits into Yii's concept, but why not adding another jQuery.ready() call after the ajax response? This should only happen, if the current view was created as a result of an ajax request…

Hardcoded test:

index.php:

<?php echo CHtml::ajaxLink("Test AjaxUpdate",


      array('site/ajaxUpdate'),


      array('replace'=>'#target')


   ); ?>

SiteController.php:

    public function actionAjaxUpdate() {


        $this->renderPartial('ajaxUpdate');


    }


ajaxUpdate.php (view):



<div id="target">


<?php echo CHtml::ajaxLink("Teste AjaxUpdate",


      array('site/ajaxUpdate'),


      array('replace'=>'#target'),


      array('id'=>'testlink2')


   ); ?>





<h2>


    Updated!


</h2><script type="text/javascript">


jQuery(document).ready(function() {                                                                                           


 jQuery('#testlink2').click( function(){jQuery.ajax({'url':'/webtraffic/index.php?r=site/ajaxUpdate','cache':false,'success':function(html){jQuery("#target").replaceWith(html)}});return false;});


});


</script>


</div>


This also won't work. Try to show the date e.g.:



<h2>


    Updated!


    <?php


       $df = new CDateFormatter('en');


       echo $df->formatDateTime(time());


    ?>


</h2>


It will be always the same.

Hmm, right. But IMO the problem is not so much, that the original link gets replaced. The problem is that new ajaxLinks don't get initialized at all.

The "Replace newtarget" link never gets its click event attached:

[code=index.php]<?php echo CHtml::ajaxLink("Replace Target",

      array('site/replaceTarget'),

      array('replace'=>'#target')

  ); ?>

<div id="target">

This is Target.

</div>

[/code]

[code=target.php]<div id="target">

<?php echo CHtml::ajaxLink("Replace newtarget",

      array('site/replaceNewTarget'),

      array('replace'=>'#newtarget')

  ); ?>

<div id="newtarget">

This is the newtarget

</div>

</div>

[/code]

[code=newtarget.php]<div id="newtarget">

Updated!

</div>

[/code]

[code=SiteController.php]<?php

class SiteController extends CController {

    public function actionIndex() {

        $this->render('index');

    }

    public function actionReplaceTarget() {

        $this->renderPartial('target');

    }

    public function actionReplaceNewTarget() {

        $this->renderPartial('newtarget');

    }

}

[/code]

I think that the best way to solve this problem is what Qiang wrote in Issue #38. We need to register new handlers in js replace/update function. This should work always.

For this moment for links and buttons we can use POS_INPLACE in htmlOptions and it works. But this doesn't solve the problem for other js based components which are not using simple "onclick" such a CTreeView.

I read that note and am not sure if i understood it right. If i did, then it doesn't solve the problem of e.g. a new ajaxLink that gets created during an ajax update (see my example above). On first page load you can't register a handler for it in "ready" because, you don't know anything about it yet.

So i still think that you might have to deliver some js with a ajax response that inits all required events that are used in that html response.

I think you're also right. But this initialization script provided with ajax response should be executed by update/replace function in "parent page", because in other way it won't be executed as you could see in your example.

So if we connect Qiang's, yours and mine ideas I think that we should get something like this in html code:



<div id="target">


  <ul id="foo" class="treeview"> ......</ul>


  <a id="bar">some ajax button</a>


  <script>


    function initTarget6() {


       jQuery("#foo").treeview({});


       jQuery("#bar"').click(..... etc..  ajax(...); initTarget7(); return false;)


    }


  </script>


</div>


....


<script>


  jQuery(document).ready(function() {


    initTarget6();


  });


</script>





</body>


I'm not sure if it will work and I'm sure if the "numbering" for initTargetXX() functions are necessary.

For this we should have posibility to registerScript in certain place ex:



<?php $cs->registerScript('target', ..., POS_IN_TAG); ?>


and this function should create initTarget function inside block element with id="target" by using preg_replace as it done for other POS_XXX functions. Additionally this funtion should run



<?php $cs->registerScript(...,  "initTarget()" , <default>); ?>


Ah ok, now it gets clearer. Thanks for the example. I would put it this way:

  1. Every view that needs js intiliaziation on the clientside (attach event handler, etc.) gets a <script> block with e.g. jQuery.yii.initViewJsXY() functions.

  2. On first page load, these functions are called from "ready" event

  3. On Ajax update with replace/update these function get's called after request is complete and update/replace was performed.

Right?

The problem could be the creation/propagation of the unique "XY" token… Because one and the same view might be used to update different targets on the same page - and thus would need different names for its init function.

Quote

1. Every view that needs js intiliaziation on the clientside (attach event handler, etc.) gets a <script> block with e.g. jQuery.yii.initViewJsXY() functions.
  1. On first page load, these functions are called from "ready" event

  2. On Ajax update with replace/update these function get's called after request is complete and update/replace was performed.

Right?

Exactly :) Thats the way I think.

Quote

The problem could be the creation/propagation of the unique "XY" token... Because one and the same view might be used to update different targets on the same page - and thus would need different names for its init function.

I think that this can be done on counter, stored for example in session data.

As you noticed each function should has name of view or id,  so it should look like  initMyTree123(), because there can be more then one views loaded by ajax on one page.

I wasn't thinking about different targets, but if the function name will be concatenation of target ID (which should be unique on page) and uniqueID for successive ajax response it should be ok.

I hope that Qiang will look at this, because if Yii get more JS based components which won't work when loaded by ajax it will be a big problem.

Yeah, I'm keeping an eye on this thread. Please keep on discussing on this if you can think of any better idea. We will try to fix this in 1.0.1 release.

BTW, I don't quite like the idea of using some session-based counters in order to make the js method name unique, because this would require starting a session, which is expensive.

I wouldn't use the target name as js function name, but the name of the view, that should be placed into the target, because target doesn't necessarily have to be a id.

That leads me to even more problems. And i'm not sure, if that's solveable at all. Lets think about this:

<?php echo CHtml::ajaxLink("Replace Target",


      array('site/replaceTarget'),


      array('replace'=>'.target')


   ); ?>





<div class="target">


    This is a target class.


</div>





<div class="target">


    This is another target class for the same update content.


</div>


That's no problem for jQuery. Target selectors can be all sorts of CSS selectors. So both targets will get updated. Now if we load the same view into both target class divs, and the view contains an ajaxLink, we would need two different init methods for both updated targets.

Since the serverside can hardly tell, how many targets there will be in the end, it doesn't know, how many different init methods will be required. Our solution will not work here. And i'm not sure, if there's a solution at all.

This would mean, that ajaxLink with replace/update must only be used, if the new content doesn't contain any elements that need js initialization.

IMO for complex scenarios like this, it's the responsibility of the programmer to make sure, that each view contains all necessary js init stuff, and call that init methods using the "complete" event of jQuery ajax.

I also think that this scenario is very complex and programmer should take care of it.

I can’t image real word situation when clicking on link in one target causes all targets to change for also the same target :D. Even if someone need something like that IMO should better reload whole page.

I hardly believe that yii can support only one target when it is "clicked" from itselve.

But I also think that one view can be rendered in more then one targets. And this situation in often. Thats why I proposed  selector based js init function name but not a view based name.  It can be also both … initViewTarget38()

I will check if this unique id's are really necessary. I'm not sure if initTarget() can be enough.

Qiang: Could you also look at #issue 47, I wrote there a new comment.