Ajax - Step By Step

Well, I have searched high and low all over this site but as a newbie I find no joy with learning how to use Ajax with Yii.

For example, I tried CHtml::ajaxLink() (copy and pasting other people’s code, since I cannot understand how they should be formatted), just to see what happened, but this just generated a regular link with a hash at the end of it. HOW do you get these things to work? Are there extra scripts that must be registered or what?

Would somebody be so kind as to post a step by step newbie tutorial on configuring Yii and creating some basic Ajax functions for it? Not all of us are experts with MVC and frameworks and jQuery :P

Well, I have managed to stumble around all day and finally have Ajax page loads happening. I don’t yet understand exactly how it works, but my guess is that there is a whole bunch of javascript in the background that I don’t see in my page source. Thats fine, at least it works somewhat.

I have found a problem however. If the page that I am loading via Ajax also contains links generated using ajaxLink(), then those links do not function.

What is the workaround for this?

for the page that’s generated through ajax use




<?php

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

?>



instead of




<?php

$this->renderPartial('view',$data)

?>



I tried that but its not working. I am using Yii 1.1

Here is my code:




// the link on my ajax generated page

echo CHtml::ajaxLink(CHtml::encode($val['name']),array('book/show','id' => $val['id']), array('update'=>'#content'), array('href' => 'index.php?r=book/show&id=' . $val['id']));



and




    // the controller that takes me there


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

      $this->renderPartial('show',array('model' => $model,

		                           'books' => $books,

		                           'pages' => $pages),false,true);

		}



Now, if I load the ajax generated page normally (right click open in new tab), then the link loads the next page using Ajax just fine, but if I load the page using Ajax it just works like a regular link, no Ajax :(

Says nada about renderPartial(); :unsure:

Fourth parameter to renderPartial controls if processOutput will be called.

Just to be sure you have jQuery initially loaded add a registerCoreScript(‘jQuery’) to your page (it is normally included if required). It will load on subsequent ajax updates.

/Tommy

Edit:

Never mind, it’s loaded since Ajax works the first time.

You may want to compare your case to this thread.

Ok, something totally, totally weird is happening.

I have a portlet in my left menu, the HTML for which is:




<ul class="categoryList">

<?php foreach($this->getCategoryList() as $category): ?>

<li>

	<?php echo CHtml::ajaxLink(CHtml::encode($category->categories_name),array('categories/show','id'=>$category->id), array('update'=>'#content'), array('href' => 'index.php?r=categories/show&id=' . $category->id)); ?>

</li>

<?php endforeach; ?>

</ul>



So far, so good. When I click on one of these links, it Ajax loads the selected category.

The category is loaded using renderPartial like so:




//snip          

                $books=Book::model()->findAll($criteria);

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

      $this->renderPartial('show',array('model' => $model,

		                           'books' => $books,

		                           'pages' => $pages),false,true);

		}



The relevant HTML for the Ajax loaded page is:




<?php

    foreach($books as $key => $val) {

     	echo '<div style="float:left; width:33%; text-align:center;">' . CHtml::ajaxLink('<img src="images/' . $val['image'] . '" alt="' . $val['name'] . '" width="100" height="100" border="0" />', array('book/show','id' => $val['id']), array('update'=>'#content'), array('href' => 'index.php?r=book/show&id=' . $val['id'])) . '<br />' . CHtml::ajaxLink(CHtml::encode($val['name']),array('book/show','id' => $val['id']), array('update'=>'#content'), array('href' => 'index.php?r=book/show&id=' . $val['id'])) . '</div>';

    }

?>



It is the above links that do not work.

What DOES happen is that if I click my menu link a second time, the following happens (I have cleared my caches):

IE8 - Clicking the menu link a second time briefly loads one of my book views, then displays the selected category. Subsequent links exhibit the same behavior. Clicking other links on the menu will load one of my book views, then briefly load the selected category, then load the book view again (actually, you dont know what view you will get, since it seems to select whatever at random.

Firefox 3.5.5 - Clicking the menu link a second time briefly loads the selected category, then switches to a view of one of my books. Clicking other links is crazy also.

This is like “link possession”… a demon is at work here :blink:

I think that the whole concept of how ajaxLink() generates “ajax links” is completely wrong. That is, it attempts to write a whole bunch of click() functions to the DOM. I can actually hear my processor cooling fan kick in as all this is being generated <_<

ajaxLink() should instead generate a link something like this:




<a href="index.php?r=categories/show&id=2"  onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=2','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;">Thriller</a>



This is explicit and there can be no confusion as to what exactly the link should do. It will also work wonderfully with Ajax loaded pages, no confusion or doubt whatsoever.

I think that what is happening above is that we are getting duplicate id’s like ‘#yt0’ written to the DOM… I certainly do not have the time to try and track the bug. I did notice a whole bunch of hair pulling on Google Code over what should be a very simple solution.

Yes, the problem might be there’s not unique id’s on each ajaxLink.

Try something like this (not tested).




<?php

foreach($books as $key => $val) {

  echo '<div style="float:left; width:33%; text-align:center;">'

  . CHtml::ajaxLink(

    '<img src="images/' . $val['image'] . '" alt="' . $val['name']

      . '" width="100" height="100" border="0" />',

    array('book/show','id' => $val['id']),

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

    array('href' => 'index.php?r=book/show&id=' . $val['id'], 'id'=>'image_'.$val['name'])

  )

  . '<br />'

  . CHtml::ajaxLink(

    CHtml::encode($val['name']),

    array('book/show','id' => $val['id']),

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

    array('href' => 'index.php?r=book/show&id=' . $val['id'], 'id'=>'text_'.$val['name'])

  ) 

  . '</div>';

}

?>



(I added ‘id’=>‘image_’.$val[‘name’] and ‘id’=>‘text_’.$val[‘name’].)

I guess that the categoryList links also need to be unique or the browser might become confused.

/Tommy

I’ve concluded that ajaxLink() requires a complete rewrite. Its functionality currently departs from the expected Yii lean and mean approach and digresses to the code bloat/complexity approach and is very inefficient and buggy.

Oh well… something interesting to do I suppose. I think the current solution for Cooper is to hard code the links using my approach, which is far simpler and guaranteed bug free, since the problem is not just probable id duplicates, but also that ajaxLink() fails on Ajax loaded pages.

As suggested, I am now coding the links in this fashion:




<ul class="categoryList">

<?php foreach($this->getCategoryList() as $category) { ?>

<li>

	<?php  echo '<a href="index.php?r=categories/show&id=' . $category->id . '"  onclick="jQuery.ajax({\'url\':\'index.php?r=categories/show&id=' . $category->id . '\',\'cache\':false,\'success\':function(html){jQuery(\'#content\').html(html)}});return false;">' . $category->categories_name . '</a>'; ?>

</li>

<?php } ?>

</ul>



Works like a charm! Not only are these links now 100% reliable, there is a noticable speed increase with the Ajax pages loading, no doubt because we have removed the overhead of using ajaxLink().

For complex AJAX applications that’s not recommended. The underlying concept is Unobtrusive Javascript. IMO it really pays of once you got used to it.

When such techniques are clearly buggy and unreliable, what is one to do? Were Yii’s implementation relatively bug free, then I would have no hesitation to use it, however clearly Cooper has done a very simple thing which by rights should work, the results of which are disastrous.

We can throw around terms such as "unobtrusive javascript" until the cows come home, however I do not see any need to introduce added complexity such as on the fly writing to the DOM when clearly a hard coded Ajax link is far simpler and will always function without fail.

I have coded extremley complex Ajax driven applications and have never resorted to the latest fashion - my livelihood depends on solutions that work, not solutions that are sexy in a coder’s eyes but nightmarishly buggy.

I’ll add my 2 cents here. I’m currently trying to use the ajaxLink in an ajax response to update a list of articles based on the selected language drop down.

There is a flaw in how Yii handles dealing with more complex ajax response that require javascript to make them work. You can use the post process option in renderPartial to get the javascript out with a rendered view.

The flaw is that you also get a link with the jquery code again, and the reason it’s a flaw is it wipes out anything you set up in jquery. For instance the ui object code i set up gets wiped out when jQuery’s main file is included with the post processed output.

The problem could be solved if there was a way to tell CClientScript not to include a script file because if your doign an ajax call then the browser’s javascript environment will have everything already unless you haven’t already included it.

The solution might be for CClientScript not to include any script file links unless specifically requested. It could have a function say registerAjaxScriptFile which then tells CClientScript you want to include that file.

Looking in firebug at the response on when process output is called vs my initial page load the link setup is in a script element on the ajax call vs the ready function for the initial load so the code somewhere knows something is up.

Yes, this nails one of the big problems, however in my opinion you run into problems even with very simple things.

If anybody cares to browse these issues on the Yii Google Code mailing list, it becomes clear that this “post processing” was a hack introduced to try and overcome the problems associated with mindless adherence to “unobtrusive javascript” standards. That is to say that these days it is anathema to actually have javascript on your web pages :blink:

There has never been a decisive fix for the known issues and their resolution has been shoved to some time in the future.

Looking at it realistically however, this adherence has turned something as simple as an Ajax link and the (simple) backend function code to handle it into a whole quagmire of needless extra code with the sole purpose of keeping javascript off web pages (never mind if you look at the actual page source, the amount of code has at least doubled). Its a crock… the latest trend and fashion. Sure there are lots of great new things, but don’t get carried away.

As a library, jQuery is absolutely fabulous, however if you want to go along with everything that is trendy, then you may as well make sure that all of your web pages are XHTML strict also (don’t brag “mine ARE”, since I know how boring such websites are).

The mentioned concept of “unobtrusive javascript” has some very valuable reasons. It isn’t “buggy” or anything but adheres to the concept of separation of layout of logic. For many people (including me) that’s an important thing and it matches with Yii’s concept pretty well. It also fits perfectly into jquery’s logic. If you look only a little around you’ll find out that it’s a widelyadopted concept. (And all those people can’t be as dumb as you might think, right? ;) )

For a starter, why inline event definition is discouraged for a long time now, maybe check this:

http://www.quirksmod…ents_early.html

Actually if you don’t like the CHtml::ajax*() methods - don’t use them! I consider them as helper for some simple cases. For anything else i use pure jquery code which is simple enough.

What did the breakthrough for me was the use of jquery.getScript(). Many of my AJAX-Actions return an executable script block. That allows me from an AJAX response to do any modification to the current page i want.

An Ajax link is not logic. It’s a link that behaves differently from a regular link. This is the thing, we are not talking about separation of layout from logic, but the separation of javascript entirely from layout (which is impossible anyway, even with jQuery). These are two different things. It is entirely impossible to separate logic from layout, even using template engines. The separation of business logic from presentation logic is a good thing, however you will never remove logic from presentation entirely. We just have two types of logic.

What we are talking about is a link such as my example as opposed to click functions written to the DOM instead. The first scenario is cleaner in all respects and adheres better to the concept of separation.

Now, let us compare the actual code side by side using Cooper’s example for a better look:

  1. creating an Ajax link using ajaxLink().



<ul class="categoryList">

<?php foreach($this->getCategoryList() as $category) { ?>

  <li>

    <?php echo CHtml::ajaxLink(CHtml::encode($category->categories_name),array('categories/show', 'id' => $category->id), array('update' => '#content'), array('href' => 'index.php?r=categories/show&id=' . $category->id)); ?>

  </li>

<?php } ?>

</ul>



  1. a typical ‘hard coded’ Ajax link using jQuery:



<ul class="categoryList">

<?php foreach($this->getCategoryList() as $category) { ?>

  <li>

    <a href="index.php?r=categories/show&id=<?php echo $category->id; ?>" onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=<?php echo $category->id; ?>','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;"><?php echo $category->categories_name; ?></a>

  </li>

<?php } ?>

</ul>



Ok, the first method is definitely less code to write, however it is a simple enough matter to write a PHP function() to output the ‘hard’ link. Both methods rely equally on the output of business logic. Technically, there is no difference in that respect.

Let us look at the HTML output which is far more important if we have a lot of Ajax links in the application:

  1. Typical output using the ajaxLink() function:



<ul class="categoryList">

  <li>

    <a href="index.php?r=categories/show&amp;id=52" id="yt0">Romance</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=53" id="yt1">Mystery</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=54" id="yt2">Thriller</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=55" id="yt3">Sci-Fi</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=56" id="yt4">Childern's</a></li>

</ul>


// all the below stuff is written to the DOM by the ajaxLink() function using Javascript:


<script type="text/javascript">

/*<![CDATA[*/

jQuery(document).ready(function() {

jQuery('#yt0').click(function(){jQuery.ajax({'url':'/suomusic/index.php?r=categories/show&id=52','cache':false,'success':function(html){jQuery("#content").html(html)}});return false;});

jQuery('#yt1').click(function(){jQuery.ajax({'url':'/suomusic/index.php?r=categories/show&id=53','cache':false,'success':function(html){jQuery("#content").html(html)}});return false;});

jQuery('#yt2').click(function(){jQuery.ajax({'url':'/suomusic/index.php?r=categories/show&id=54','cache':false,'success':function(html){jQuery("#content").html(html)}});return false;});

jQuery('#yt3').click(function(){jQuery.ajax({'url':'/suomusic/index.php?r=categories/show&id=55','cache':false,'success':function(html){jQuery("#content").html(html)}});return false;});

jQuery('#yt4').click(function(){jQuery.ajax({'url':'/suomusic/index.php?r=categories/show&id=56','cache':false,'success':function(html){jQuery("#content").html(html)}});return false;});

});

/*]]>*/

</script>



  1. Regular Ajax link:



<ul class="categoryList">

  <li>

    <a href="index.php?r=categories/show&id=52" onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=52','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;">Romance</a>

  </li>

  <li>

    <a href="index.php?r=categories/show&id=53" onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=53','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;">Mystery</a>

  </li>

  <li>

    <a href="index.php?r=categories/show&id=54" onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=54','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;">Thriller</a>

  </li>

  <li>

    <a href="index.php?r=categories/show&id=55" onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=55','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;">Sci-Fi</a>

  </li>

  <li>

    <a href="index.php?r=categories/show&id=56" onclick="jQuery.ajax({'url':'index.php?r=categories/show&id=56','cache':false,'success':function(html){jQuery('#content').html(html)}});return false;">Children's</a>

  </li>

</ul>



I am certain as to which I would would prefer to be my page source, since I often create fully Ajaxed websites and all that DOM writing, which would be monumental and which relies on the client memory and processor to output, would be a massive performance hit ;)

There is absolutely no technical merit to the ajaxLink() method. It does not separate logic, there is no less coding, it bloats the output source code, it adversely affects performance and relies on the client machine for processing and most importantly, is inherently buggy.

The problems with using ajaxLink() within Ajax loaded pages are well documented and unresolved and introduce even more problems such as those encountered by Cooper.


<ul class="categoryList">

  <li>

    <a href="index.php?r=categories/show&amp;id=52" class="category" id="c52">Romance</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=53" class="category" id="c53">Mystery</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=54" class="category" id="c54">Thriller</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=55" class="category" id="c55">Sci-Fi</a></li>

  <li>

    <a href="index.php?r=categories/show&amp;id=56" class="category" id="c56">Childern's</a></li>

</ul>


<script type="text/javascript">

/*<![CDATA[*/

jQuery('a.category').click(function() {

    var id=$(this).substr(1);

    jQuery.ajax({

        'url'       :'/suomusic/index.php?r=categories/show&id='+id,

        'cache'     :false,

        'success'   :function(html){ jQuery("#content").html(html) }

    });

    return false;

});

/*]]>*/

</script>



No comment.

Well, I most certainly do have a comment.

  1. Scenario: Every single link in my application is Ajax.

  2. This discussion is about Yii and in particular it’s Ajax implementation and the ajaxLink() function (which falls on it’s ass).

  3. Considering that we are also talking about application wide Ajax links, then using your method we must write a whole bunch of these functions. This introduces nothing other than unnecessary LOGIC. An Ajax link is an Ajax link (no logic): only a SINGLE function() is required to handle them (the response), not a thousand!

  4. I understand that you believe what you are doing is clever, but the real reason for it is some unexplained aversion to onclick(). There is no real explanation for it other than an unreasoned desire (and attempt only) to remove javascript from markup. Truth is, you have not removed it at all, you have simply moved it… no, you have introduced a whole bunch more javascript (and logic) in a different part of your XHTML (it is still within it). This is just a silly fashion, nothing more.

Conclusion: rather than remove javascript logic from your XHTML, you have only managed to introduce swathes of new and unneeded logical functions to replace simple Ajax links (which do not contain logic).

I don’t see the problem, just overwrite the default id by giving the link an unique id yourself.

just add:

‘id’=>‘MyUniqueId’

to the html options array and your problem is solved

(replace MyUniqueId by an actual unique Id of course)