ajaxButton, ajaxLink and links with confirm param

I’ve tested and … I have good news :D

  1. It's working

  2. It's working without unique ID's (tested under FF and Konqueror, someone could test it with IE).

SiteController.php:



    public function actionAjaxUpdate() {


        $this->renderPartial('ajaxUpdate');


    }


someware in views/site/index.php :



$this->renderPartial('ajaxUpdate');


and the most important part views/site/ajaxUpdate.php:



<div id="target">


<?php echo CHtml::link("Test AjaxUpdate", '#', array('id'=>'testlink')); ?>





<h2><?php $df = new CDateFormatter('en'); echo $df->formatDateTime(time()); ?></h2>





<script type="text/javascript">


	function initTarget() {


		jQuery('#testlink').click(function(){jQuery.ajax({'url':'/frk/yii_test3/index.php?r=site/ajaxUpdate','cache':false,'success':function(html){


			jQuery("#target").replaceWith(html);


			initTarget();


		}});return false;});


	}


</script>





<?php Yii::app()->getClientScript()->registerScript('target',  'initTarget();'); ?>





</div>


Please remember to set correct url in JS ajax request …

So i think that:

  1. registerScript should have ability to register script in view's body in js function.

  2. renderPartial should render script block with this init function where would be registered all scripts from widgets.

  3. all widgets which use js, when are rendered by renderPartial() method should register their initialization script in view's body. 

  4. these widgets should register init function in global document.ready (for first time initialization when whole page is loaded)

Robak, I verified that the example code works in IE6/IE7. Tried two instances (with unique identifiers).

Due to my security settings IE6 asks for "ActiveX controls or plugins" as well as script access to the control/plugin. Surprisingly IE7 asked for the same permission (should not need to load XMLHTTP but jquery.js claims the XMLHttpRequest isn't properly implemented in IE7).

/Tommy

Nice to hear that ist's working on IE. I'm not JS specialist but I think that it is strange why IE asks for such a permition. Maybe IE means some kind of "active content" …

Qiang, what do you think about this code? Does it fit all yii's requirements?

The XMLHTTP component is needed by Ajax in general. Consequently, those IE users who disabled (or rejected at runtime) ActiveX controls, will miss the Ajax functionality. Flash falls into this category too, unfortunately.

/Tommy

Tri, I don’t get the point. If someone disable or reject ActiveX it won’t work as he wish :D    When someone disables something that means that he want to have it disabled. For me it is correct.

E.g. I'm using FF with noScript plugin and I have JS disabled by default. That means that I don't want to have I enabled … When I what to use JS based page a have to enable it.

I agree, my comment about ActiveX was a bit off-topic. But it still might be of interest to somebody with no IE at hand. Unfortunately, IE does not give a hint about which control/plugin it is about to load. (From my own surfing experience, I can tell many sites doesn't work at all if I reject ActiveX when prompted.)

/Tommy

Thank you for all your investigations!

robak: I will use your approach. There is a minor problem here: initTarget function should NOT be put in jQuery ready function if it is in the result of a replace/update AJAX response. Otherwise, on IE initTarget will be called twice and the same click function will be called twice as well (on FF, the click function is only called once).

Due to this issue, we will need to differentiate the AJAX response generated due to a replace/update request from other AJAX requests, because other AJAX requests may need initTarget to be called inside jQuery's ready function.

So I will add an additional parameter to CController::processOutput to handle this problem.  And the usage becomes the following:

For replace/update ajax actions, call the following to render the partial view:



$output=$this->renderPartial('view',null,true);


// the second parameter indicates that initTarget should


// not be placed in the ready function


echo $this->processOutput($output,false);


For other ajax actions, call the following like you did before:



$this->renderPartial('view',null,false,true);


A bit twisted, but seems necessary.

Comments?

I don't really understand what you mean.

We have something like this:



<body>





<div id="target">


  <script>


  // definition of initTarget();


  </script>


</div>





<script>


// on ready initTarget();


</script>





</body>


On ajax response we replace/update  only [tt]<div id="target"> </div>[/tt] block, and there is no jQuery ready function. The initTarget() call in outer script is needed for first time rendering. Inner initTarget() definition I think that is needed because each time when we replace this block there would be no initTarget() definition for next time replace/update click. Am I wrong ?

Do you mean that IE calls again outer jQuery ready function on ajax response, when the whole page is not reloaded? 

I think that this is not a big problem if there is needed to add extra params to renderPartial when rendering for AJAX replace/update.

We all agree that for the initial page, initTarget() should be placed inside ready(). So I am talking about the result of renderPartial due to an ajax request.

initTarget() function will be redefined for every request.

For update/replace ajax requests, we will call initTarget() after update/replace.

By default, initTarget() will also be called by jquery ready. By using the extra parameter, we can change this behavior to avoid initTarget() from being invoked twice (one in ready, and one after update/replace) during an update/replace response.

Quote

By default, initTarget() will also be called by jquery ready. By using the extra parameter, we can change this behavior to avoid initTarget() from being invoked twice (one in ready, and one after update/replace) during an update/replace response.

Forgive me but today I'm a little bit tired so still don't understand …

  1. Whole page GET request.

  2. Whole page response with renderPartial in the whole page view.

  3. initTarget defined in #target is called by jquery ready.

  4. Page is ready.

  5. We click on ajaxLink.

  6. On click we are calling renderPartial.

  7. #target is replaced with response from renderPartial with new initTarget definition (same as previuos).

  8. new initTarget() is called by old script.

  9. Page is ready.

  10. LOOP 5.

Where is this problem? In point 8. or somewhere else?

How do you want change "this behavior to avoid initTarget() from being invoked twice" ?

What you described is the cycle for ajax replace/update responses (from step 5 to 10), where initTarget() is called only after step 7.

The problem I was describing is for ajax responses that do not replace or update existing elements. In that case, initTarget() will be called by jquery ready.

So as you see, we have two different scenarios: in replace/update scenario, initTarget is called only after step 7, and in the rest scenario, initTarget is called (as usual) in jquery ready.

We need a way to differentiate between these two scenarios. That's why I was proposing the extra parameter used during renderPartial to generate the ajax response.

I was thinking that way:

When update/replace is set in ajaxLink then we render



function initTarget() {


......


  initTarget();


}


in the other scenarios we render initTarget function without this extra initTarget() call so it should be called only once on jquery ready…

… I think I know now what do you mean… The situation when something is replaced but it doesn’t change the script block, then extra initTarget call is not needed …  so the extra parameter in renderPartial is needed… I was thinking about it at the beging of the topic, but forgot :)

Hi,

don't know if it helps and i haven't tried it yet, but i found some tutorial with 3 jquery plugins for this problem:

Tutorial:

http://www.learningj…h-events-part-1

http://www.learningj…h-events-part-2

jQuery plugins:

Live Query http://plugins.jquer…oject/livequery

Listen http://plugins.jquer…/project/Listen

Intercept http://plugins.jquer…oject/Intercept

Greetings from Germany

me23

Thank you for your pointers.

I also found this might be a jquery issue reported at http://groups.google…c9f43b859?hl=en

I will have to re-read all of this thread a couple of times to understand everything (I’m not an experienced web programmer). But for now I can report some new findings related to my recent testing with IE:

Since the updates felt sort of non-responsive I looked for better resolution and ended up with a call counter (stored in session between invocations). If I click the link with a rate of approx. 4 Hz, it seems like IE will miss approx. 30% of the click events. That is, the counter isn't incremented. IE6/IE7 both behaves this way (I also forced IE7 to use XMLHttpRequest, but still the same). With FF3 all counter increments will eventually show up.

I must admit I got puzzled over the "self-modifying code", but at that time assumed it's just overwritten in-place. I guess it's undefined how the different browsers implement the element replacement? Strictly appending, inserting or just using the first free slot. And, since I don't know, I guess it also might be a race condition if the callback runs on another thread? Would certainly explain why I have these missed click events. Just my thoughts…

/Tommy

Edit:

It turns out IE behaves like it had a click hold-off time of 400-500 ms. Turnaround time less than 50 ms until click event is rearmed. I guess this is good when clicking a link but might be a problem in other cases. Could be JS-related or IE itself, I don't know.

Finally I'm beginning to understand what the recent part of the discussion is about. I couldn't verify that InitTarget gets called twice.

In this case it wouldn't because initTarget gets called in the success callback handler only (as a result of a click event):



<script type="text/javascript">


  function initTarget() {


    jQuery('#target')


    .click(function(){


      jQuery.ajax({


        'url':'/MyApp/index.php?r=site/ajaxUpdate',


        'cache':false,


        'success':function(html){


          jQuery("#target").replaceWith(html);


          initTarget();


        }


      });


      return false;


    });


  }


</script>


In this case it would because initTarget gets called immediately after defining the function (as well as from document.ready in case of a full page reload):



<script type="text/javascript">


    function itemGrid()


    {


      //jQuery(".even,.odd")


      jQuery("#item_Grid").find(".even,.odd")


        .hover(


          function(){ $(this).css({"saved-bg": $(this).css("background")}); $(this).css({"background":"#bbbb00"});},


          function(){ $(this).css({"background": $(this).css("saved-bg")});}


        )


        .click(


          function(){


            jQuery.ajax({


              url:"/MyApp/index.php?r=item/ajaxUpdate",


              cache:false,


              data:"id=" + $(this).attr("id"),


              success:function(html) {jQuery("#item_target").replaceWith(html); }


            });


          });


    }


    //itemGrid();


    setTimeout(itemGrid, 100);


</script>


In the latter example I added a find() to discriminate between the two grids that will be used on the same page. As a result the function stopped working both in FF and in IE. I ended  up trying with the setTimeout call and now it's working again. For instance 10 ms wasn't enough. I don't know the exact reason but it seems like the delay and/or a possible yield of execution overcomes the problem.

Happy New Year to all of you

/Tommy

FYI this works in IE (.hover() color change):



function(){ $(this).css({"-tri-saved_bg": $(this).css("background-color")}); $(this).css({"background":"#bbbb00"});},


Attribute name uncritical but in this example changed to proposed vendor specific format.

BTW, this works too:



function(){ $(this).attr("saved_bg", $(this).css("background-color")); $(this).css({"background":"#bbbb00"});},


So the difference is that in IE “background-color” must be used (instead of “background”).

Any one took a look at this? http://brandonaaron…docs/livequery/

I gave it a quick try a while ago, but felt I have to gain more understanding… which I still haven't.

Anyway, this is what I did, and it seems to work (I still don't understand exactly when and why jQuery is or isn't in control).

View file:



<script type="text/javascript">


var tricount=0;


...


...


  $('#target a') 


    .livequery('click', function(event) { 


      alert('clicked, tricount : '+tricount);


      jQuery.ajax({


        'url':'/AppYii10/index.php?r=site/ajaxUpdate',


        'cache':false,


        'success':function(html){ /*jQuery("#target").replaceWith(html);*/ jQuery("#target").html(html);}


      });


      return false; 


    }); 


</script>


...


...


<div id="target">


<?php $this->renderPartial('ajaxUpdate'); ?>


</div>


AjaxUpdate.php:



<!-- <div id="target"> -->


<?php echo CHtml::link("Test AjaxUpdate", '#', array('id'=>'testlink')); ?>


<?php echo 'counter= '.$count ?>


<h2><?php $df = new CDateFormatter('sv'); echo $df->formatDateTime(time()); ?></h2>


</div>


<!-- </div> -->


liveQuery obviously use it's own structures and GUID:s to keep track of which elements are still existing. My counter, which is incremented inside the liveQuery code shows an initial count of 90 (seemingly including the 20ms delay), traversing the whole DOM. Every click adds 3 to the count (obviously only replaced parts).

Someone else had a look?

/Tommy