Fly Yahoo UI

March 28th, 2006. Tagged: Ajax, JavaScript, yui

Here goes the rhyme:
Make your content management application fly
with the Yahoo library of JavaScript UI...

Making fancy UI stuff has never been easier. Google released their JavaScript XSLT library, Prototype is everywhere, others too... now Yahoo! released their UI library. I took a look at Y! stuff, let me show you what I did.

ยป Before you continue, feel free to check the demo at any time

CMS

OK, let the first one who has never coded some sort of Content Management System throw a stone. Yep, that's what I thought. Writing a CMS is like the new Hello World ;)

A very simplistic CMS would have a bunch of data records and options to add new records, to modify existing entries or to delete them. Let's see how to do the deletion fly, web 2.0. style.

The table

The records are stored in a very, very basic table that has its markup to the bare minimum. The table has an ID "records" and every link also has an id like "delete-1", "delete-2", etc, where the numbers are the record IDs from the database table. The beauty of this is that the links go to delete.php, a server side script that will delete the requested record and return something. So this will work even for JS-disabled browsers. For those that are enabled though, we'll make the links not navigate to delete.php, but send a small AJAX request to that same server-side script.

Here's the table markup, as you can see, very minimalist.

<table id="records" align="center">
  <caption>Table with records in it</caption>
  <tbody>
    <tr>
      <th>Last Name</th>
      <th>First Name</th>
      <th>&nbsp;</th>
    </tr>
    <tr>
      <td>Man</td>
      <td>Super</td>
      <td><a href="delete.php?id=1" class="delete" id="delete-1">delete</a></td>
    </tr>
    <tr>
      <td>Woman</td>
      <td>Cat</td>
      <td><a href="delete.php?id=2" class="delete" id="delete-2">delete</a></td>

    </tr>
    <tr>
      <td>Man</td>
      <td>Invisible, The</td>
      <td><a href="delete.php?id=3" class="delete" id="delete-3">delete</a></td>
    </tr>
  </tbody>
</table>

Attach an event

To make the links call a JavaScript function, we'll have to attach an event to them. For this, let's use Y!s event library (event.js). Here's all that's needed:

// add an onclick listener to the records table
YAHOO.util.Event.addListener('records','click',clickHandler);

Very simple, right? Now we've attached an event to the whole table. That's far more convenient than attaching an event to each and every link. With the line above we stated our desire that whenever there is a click somewhere inside the table, the function clickHandler() is executed. Let's see what's in there.

clickHandler()

function clickHandler(e) {
    // make sure the default event (vistiting the link) is not executed
    YAHOO.util.Event.preventDefault(e);
    // which element was clicked (target is in W3C, srcElement is in IE)
    var target = (e.srcElement) ? e.srcElement : e.target;
    // if the target element has class named "delete", that's our dude
    if (target.className == 'delete') {
         if (confirm('Sure you wanna delete?')) { // be polite, ask first
            // figure out which record to delete
            var id_to_delete = target.id.replace('delete-','');
             // the ajax stuff
            YAHOO.util.Connect.initHeader('my_ajax','oh yes it is');
            YAHOO.util.Connect.asyncRequest (
                'GET',
                'delete.php?id=' + id_to_delete,
                {success: removeRow,
                 failure: function (xhr) {
                                alert('Error :( try later...');
                           },
                 cell_id: target.id
                }
            );
        }
    }
}

As you see, I've put quite a bit of comments to illustrate what's going on at each line. Maybe the AJAX part will still need some more details though.

First, the header setting. Using YAHOO.util.Connect.initHeader we'll send an additional HTTP header, called my_ajax with some funny value. The purpose of this exercise is to tell our server-size delete.php script that this is an AJAX request and it will return a different response once it does its thing.

The actual request is made using YAHOO.util.Connect.asyncRequest. We pass the request method (GET), the URL (delete.php?id=12345) and then we pass an object to tell that if the request was successful, the function removeRow() should be called. On failure, an anonymous function is executed to simply alert that something's wrong.

The success and failure methods form the so-called callback object. You need a callback to instruct the AJAX call what to execute next. In the callback object you can also pass anything you like, like the property cell_id for example.

removeRow()

This function will be executed once the AJAX call completes successfully. In the function we'll:

  1. Check if the server-side returned "ok" response.
  2. Paint the row to be removed red
  3. Animate the row until it fades
  4. Remove the row completely

You may think that part 1 of this plan is funny? Laugh all you want, but if you think about it, it may start making sense ;) I mean you're right, it's AJAX, it should be Asynchronous JAvascript and XML. But we don't need no stinkin' XML for such a minor thing. A simple, friendly 'ok' is all it takes. No <root>s, no getElementsByTagName().item() end so on.

One hiccup in the plan is that the Yahoo UI doesn't yet support color animation. But we can use opacity animation. Good, great. Only that animating the opacity of a table row doesn't work in IE6. Shoot! Solution? Loop though the cells of the row and animate them. It will work.

Once all is animated and done, the last thing is to remove the row. It's easy, but we want to make sure it's done only after the animation is completed. Hence the "subscription" the Y! library provides.

Here's the function in its whole, see the comments.

function removeRow (xhr)
{
    if (xhr.responseText != 'ok') {
      alert('Error, try later...');
      return;
    }
    // now let's remove the row that contained the record
    var row = document.getElementById(this.cell_id).parentNode.parentNode;
    row.style.backgroundColor = '#ff0000';  // make it red

    // will make the removal animated
    var attributes = {opacity:{from:1, to:0}}; // will animate until opacity is 0
 
    // loop through each cell and animate it
    // animating the opacity of a row (not all cells) is preferable,
    // but it's not happening on IE
    for(var i=0, thecells = row.cells; i < thecells.length; i++) {
        var anim = new YAHOO.util.Anim(
            thecells[i], // animate what
            attributes,  // animate how
            1,           // for how long
            YAHOO.util.Easing.easeOut // and a dynamics touch
        );
         if (i == thecells.length - 1) {
            anim.onComplete.subscribe( // execute on completion
              function(){
                row.parentNode.removeChild(row); // remove the row
              }
            );
        }
        anim.animate();
    }
}

The server-side check

As mentioned earlier, delete.php will delete the row regardless of how it's called - directly from the browser or through the AJAX call. The difference is that the response will be different. How does delete.php know how to respond? Looking at the extra header we sent, of course! Here goes:

<?php
// First off, do the actual record deletion
// check for permissions, etc
 
// When it comes to reporting,
// respond differently if called with AJAX request or normally
if (!empty($_SERVER['HTTP_MY_AJAX'])) {
    // this is an AJAX request
    // the response can be XML, JSON, whatever
    // for my purposes, a simple status reply is enough
    echo 'ok';
} else { // a normal browser request
    // do something, like header('Location: ...') for example
}
?>

Conclusion

So that's it, hope you enjoyed the trip. Once again, the demo is here, the Yahoo UI is here and ... have fun with the Y! library! Though despite the excitement of the new toy, please do not forget to sleep daily, it's important (I hear) ;)

Tell your friends about this post: Facebook, Twitter, Google+

Sorry, comments disabled and hidden due to excessive spam. Working on restoring the existing comments...

Meanwhile, hit me up on twitter @stoyanstefanov