Grow your CSS skills. Land your dream job.

Creating a Web App from Scratch – Part 6 of 8: Adding AJAX Interactivity

Published by Chris Coyier

Our developer has already done a massive amount of work turning this idea into a real application. Now let's make some more for him! The most important part of this app is creating and managing your list. We decided from the get-go that this was going to be an AJAX-y app. We didn't chose AJAX because it's a popular buzzword, we chose it because we know it's the best path toward making a responsive, easy to use, natural feeling application on the web.

The Big Thing: Saving the List

AJAX allows for us to send requests and get responses from the server without a page refresh. In our app, that functionality is going to be used primarily for saving. Let's think through each of those times something needs to be saved:

  • When a new list items is added, it should be saved to the list.
  • When a list item is deleted, it should be deleted from the list.
  • When an items color is changed, the new color should be saved.
  • When a list item is marked as done, that status should be saved.
  • When the list is reordered, the new order should be saved.
  • When the text of a list item is altered, the new text should be saved.

That's a lot of saving going on. Our developer has his work cut out for him, because each of those little events needs to have a PHP file that is ready to receive that request and deal with it. Fortunately he has some cool object oriented stuff gong on already and can certainly extend that to deal with this.

Interface JavaScript

Alongside all that AJAX saving is all the stuff that makes the interface do what visually it's saying it will do. That little drag tab is saying it can drag list items up and down. We are saying after that happens we are going to save the list. But how does that actually work? Don't worry, we'll get to it. For now let's think through all the interface JavaScript things that we need:

  • Click and drag the drag tab, list items can be dragged around and reordered.
  • Click the color tab, the list items color is toggled between some predefined choices.
  • Click the checkmark, the list item crosses out and and fades down.
  • Click the X, a confirmation slides out. Click again, list item is whisked away.
  • Double-click the list item, text turns into a text-input for editing.
  • Type in the large box below and click add, new list item is appended to bottom of the list.

And again, we're going to get to all that. Just a little more setup to do!

First things first: calling the JavaScript files

There is really only one page on our site, the main list page, that needs JavaScript at all. So we'll be dropping the script files right into the index.php file. You'll often see JavaScript file linked in the header of sites. This isn't the time for a lengthy discussion about that, but suffice it to say that that isn't required as it's generally considered a performance enhancement to list them at the end of pages instead. That's just what we'll do here. In our index.php file, after we've output the list, we'll call the JavaScript we need.

<script type='text/javascript' src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js?ver=1.3.2'></script>
<script type="text/javascript" src="js/jquery-ui-1.7.2.custom.min.js"></script>
<script type="text/javascript" src="js/jquery.jeditable.mini.js"></script>
<script type="text/javascript" src="js/lists.js"></script>
<script type="text/javascript">
    initialize();
</script>
  1. Load jQuery (from Google for better speed)
  2. Load a customized jQuery UI library (for the draggable stuff)
  3. Load the jEditable plugin (for click-to-edit)
  4. Load our custom script
  5. Call the initialization function (our own kickstarter, kind of like a DOM ready statement)

Cleaning up the Markup with JavaScript

In our custom lists.js file, we'll be creating a number of different functions. The very first one is the one that gets called directly in the index.php file after the lists have been output:

function initialize() {

};

The first thing we are going to do there is clean up the markup a bit by inserting common elements with JavaScript rather than have them directly in the markup. Remember what the markup for the list looked like when we mocked it up?

<ul id="list">
   <li class="colorRed">
        <span>Walk the dog</span>
        <div class="draggertab tab"></div>
        <div class="colortab tab"></div>
        <div class="deletetab tab"></div>
        <div class="donetab tab"></div>
    </li>

    <!-- more list items -->

</ul>

Much of that is redundant across all the list items. What we want is more like this:

<ul id="list">
   <li class="colorRed">
        Walk the dog
    </li>

    <!-- more list items -->

</ul>

All the divs and the span has been removed. The class name on the list item is fine, because PHP will be spitting that out for us when it reads the database and outputs the list.

How do we append all that extra HTML? Easy with jQuery. Target the list items and wrap each of the the innards using the wrapInner() function and append the extra divs with the append() function.

function initialize() {

  // WRAP LIST TEXT IN A SPAN, AND APPLY FUNCTIONALITY TABS
  $("#list li")
    .wrapInner("<span>")
    .append("<div class='draggertab tab'></div><div class='colortab tab'></div></div><div class='deletetab tab'></div><div class='donetab tab'></div>");

};

Bind events to the new functionality tabs, the smart way
Binding an event to an HTML element is pretty easy in JavaScript. It's like this:

$("li").click(function() {
   // do something
});

There is nothing wrong with that, but we are in a bit of a unique situation with our list app here. When you bind events like that 1) it creates a unique event handler for every single list item on the page, each one taking up browser memory and 2) it only does it once, for the current state of the DOM. Don't worry about all that too much, the point is binding events this way isn't ideal for us because we will be inserting new list items dynamically.

When a user inserts a new list item, that gets plugged into the DOM right then and there. That new list item will not be bound as the others are, meaning all those fancy little tabs won't work right. Boo hoo. What can we do to solve that? Well we can create a new function that will be called when the page loads and when new list items are appended that does all that event binding work. That will definitely do the trick, but... jQuery is smarter than that. jQuery provides a function called live() that eliminates this problem entirely.

$("li").live("click", function() {
   // do something
});

Binding events with the live() function is fantastic for us because 1) it only creates one event handler which is far more efficient and 2) new items appended to the page are automatically bound by the same handler. Killer.

So for our little functionality tabs, we'll be using them like this:

$(".donetab").live("click", function() {
   // do stuff
});

$(".colortab").live("click", function(){
   // do stuff
});

$(".deletetab").live("click", function(){
   // do stuff
});

The drag tab doesn't have click event, it's actually going to use jQuery UI's draggable functionality to do it's thing. Let's check that out.

Making the list drag / sortable

Mega thanks to jQuery UI for making such a useful set of functions. The draggable module is exactly perfect for making a list like our sortable. We target the parent <ul>, tell it the "handle" we wish to use (which part of the list item you can click and drag to move them). We also use a parameter forcePlaceholderSize for some visual feedback when the list items are dragged around (a white block space pops in to indicate where the list item would "land" if released)

$("#list").sortable({
    handle   : ".draggertab",
    update   : function(event, ui){
       
        // Developer, this function fires after a list sort, commence list saving!

    },
    forcePlaceholderSize: true
});

Marking items as "done"

When the user clicks the little checkmark tab, we have already decided to do two things. Draw a line through the list item and then fade that whole list item out. But then there is the consideration of what to do if the item is already marked as done and that tab is clicked. Well, we'll uncross it out and fade it back up. So when the click happens, we'll make sure to check which state we are in first.

$(".donetab").live("click", function() {

    if(!$(this).siblings('span').children('img.crossout').length) {
        $(this)
            .parent()
                .find("span")
                .append("<img src='http://cdn.css-tricks.com/images/crossout.png' class='crossout' />")
                .find(".crossout")
                .animate({
                    width: "100%"
                })
                .end()
            .animate({
                opacity: "0.5"
            },
            "slow",
            "swing",
            function() {
                           
                // DEVELOPER, the user has marked this item as done, commence saving!

            })
    }
    else
    {
        $(this)
            .siblings('span')
                .find('img.crossout')
                    .remove()
                    .end()
                .animate({
                    opacity : 1
                },
                "slow",
                "swing",
                function() {
                           
                // DEVELOPER, the user has UNmarked this item as done, commence saving!

            })
            
    }
});

Color Cycling

We'd better get on this whole "colored" part of Colored Lists eh? CSS will be applying the actual color, so what we'll be doing with JavaScript is just cycling the class names applied to those list items on clicks.

$(".colortab").live("click", function(){

    $(this).parent().nextColor();

    $.ajax({
       
        // DEVELOPER, the user has toggled the color on this list item, commence saving!

    });
});

That nextColor() function isn't a built-in function, it will be custom written by us. It's abstracted away here for code clarity. The way that we've used it here (as a part of the "chain") is such that we'll need to make a little jQuery plugin out of it. No problem.

jQuery.fn.nextColor = function() {

    var curColor = $(this).attr("class");

    if (curColor == "colorBlue") {
        $(this).removeClass("colorBlue").addClass("colorYellow").attr("color","2");
    } else if (curColor == "colorYellow") {
        $(this).removeClass("colorYellow").addClass("colorRed").attr("color","3");
    } else if (curColor == "colorRed") {
        $(this).removeClass("colorRed").addClass("colorGreen").attr("color","4");
    } else {
        $(this).removeClass("colorGreen").addClass("colorBlue").attr("color","1");
    };

};

Basically this check what color the list item already is and moves it to the next color. Notice how we are altering an attribute on the list items too. Color isn't a valid attribute in XHMTL (it's fine in HTML5), but oh well. It's not in the markup so it doesn't really matter. Why are we using this? We'll, it's because we are about 50% of they way in doing this really intelligently. When our developer goes to save the color information about this list item to the database, he needs something to save. Back in Part 2 of this series, we can see that our developer already anticipated this and created a field for color called listItemColor, which he made an INT (integer). He figured that would be the smartest way to do it as it's lightweight, easy, and abstract. We can decide later what they key is, e.g., 1 = Blue, 2 = Red, etc. The data itself doesn't need to know it's red. So, if we have an integer representing the color right in the DOM for him, that makes it really easy to snag out and pass along to save to the database.

Why is this only 50% smart? Well, because we should probably extend that smartness to the class names themselves. We are using colorYellow for example, when color-1 might make more sense, if down the line we decide to drop yellow from the lineup and replace it. Or even perhaps let users declare their own colors.

Deleting list items

Our little "X" tab is in charge of allowing users to delete list items. We want to have a little extra insurance against accidentally fat-fingerings though. So we are going to require two clicks to actually delete something. Some applications resort to a nasty "ARE YOU SURE" modal popup dialog box, we'll be a little more sly than that. As you click the X, a little notice will pop out to the right asking about sureness. If they click again then deleting may commence.

$(".deletetab").live("click", function(){

    var thiscache = $(this);
            
    if (thiscache.data("readyToDelete") == "go for it") {
        $.ajax({
          
              // DEVELOPER, the user wants to delete this list item, commence deleting!

              success: function(r){
                    thiscache
                            .parent()
                                .hide("explode", 400, function(){$(this).remove()});

                    // Make sure to reorder list items after a delete!

              }

        });
    }
    else
    {
        thiscache.animate({
            width: "44px",
            right: "-64px"
        }, 200)
        .data("readyToDelete", "go for it");
    }
});

Because we were smart earlier and our little tab graphic is all a part of one sprite graphic, all we need to do is expand the width of that tab to display the message. After the first click, we append a little bit of data (jQuery's data() function) to that list item saying to "go for it". Upon a second click, that test will be TRUE and we know we can commence the deletion of that list item.

Since we are using jQuery UI, we tossed in a little extra fun flair with the "explode" option for hiding elements.

Click-to-edit list items

In order to make our list items click-to-edit, we'll stand on the shoulders of others and use a jQuery plugin, jEditable. All we need to do with this plugin is target an element and use the editable() function on it with some parameters. On big caveat though, we can't use the live() function with this plugin because it's not a standard jQuery event.

Back before we had live, we did what we talked briefly earlier. We called a function that did all our binding. That way we could call it on DOM ready as well as after any AJAX insertions. We'll lean on that technique now.

function bindAllTabs(editableTarget) {
   
    $(editableTarget).editable("/path/for/DEVELOPER/to/save.php", {
        id        : 'listItemID',
        indicator : 'Saving...',
        tooltip   : 'Double-click to edit...',
        event     : 'dblclick',
        submit    : 'Save',
        submitdata: {action : "update"}
    });
    
}

With those parameters, we're giving the developer what he needs for a callback file path. We're also declaring that we want list items to require a double-click to edit, what the tooltip will be, and what the text in the saving button will say. Very nicely customizeable!

Remember those spans we inserted around the text of the list items earlier. Now we are cashing in on that. When we bind this editable function, we'll bind it to those spans. So right after DOM ready we'll call:

bindAllTabs("#list li span");

We're going to need this function again when appending new list items...

Appending new list items

Core to the functionality of our list app is allowing people to add new list items. We already have the markup in place for that, so let's look at the JavaScript that powers it.

$('#add-new').submit(function(){

    var $whitelist = '<b><i><strong><em><a>',
        forList = $("#current-list").val(),
        newListItemText = strip_tags(cleanHREF($("#new-list-item-text").val()), $whitelist),
        URLtext = escape(newListItemText),
        newListItemRel = $('#list li').size()+1;
    
    if(newListItemText.length > 0) {
        $.ajax({
           
                // DEVELOPER, save new list item!

            success: function(theResponse){
              $("#list").append("<li color='1' class='colorBlue' rel='"+newListItemRel+"' id='" + theResponse + "'><span id=""+theResponse+"listitem" title='Click to edit...'>" + newListItemText + "</span><div class='draggertab tab'></div><div class='colortab tab'></div><div class='deletetab tab'></div><div class='donetab tab'></div></li>");
              bindAllTabs("#list li[rel='"+newListItemRel+"'] span");
              $("#new-list-item-text").val("");
            },
            error: function(){
                // uh oh, didn't work. Error message?
            }
        });
    } else {
        $("#new-list-item-text").val("");
    }
    return false; // prevent default form submission
});

NOTE: Stay tuned for the final part in the series where will go over some extra security that needs to happen here. Anytime we accept input from the user, it needs to be scrubbed. This is no exception.

One last thing we did there was to clear the input field after submission. That makes adding a bunch of list items in sequence very easy and natural. Also, note that we called that bindAllTabs function again after the new list item was appended. That's why we abstracted that function away to begin with, so we could call it when new list items were appended AJAX style. That ensures the click-to-edit functionality is working on newly appended list items even before a page refresh. The rest of the tabs are automatically cool, due to the live() binding.

Moving On

Up next we'll pass it back to the developer for filling in those gaps on how those list interactions work from the PHP / Database side. Then we'll finish up talking a little security and wrapping up any loose ends.

Series Authors

Jason Lengstorf is a software developer based in Missoula, MT. He is the author of PHP for Absolute Beginners and regularly blogs about programming. When not glued to his keyboard, he's likely standing in line for coffee, brewing his own beer, or daydreaming about being a Mythbuster.
Chris Coyier is a designer currently living in Chicago, IL. He is the co-author of Digging Into WordPress, as well as blogger and speaker on all things design. Away from the computer, he is likely to be found yelling at Football coaches on TV or picking a banjo.

Comments

  1. Sid
    Permalink to comment#

    Awesome stuff. You guys have done a lot in a short time.

  2. MikeMc
    Permalink to comment#

    probably one of the best tutorials I’ve followed. Couldn’t come at a better time as I’m trying to learn both jQuery and PHP at the moment. Kudos to both of you and thanks for putting this together.

  3. Leo Rapirap
    Permalink to comment#

    This is very helpful. Thanks guys for taking time to write this very informative article.

  4. Alidad
    Permalink to comment#

    are they any demo that we can see!

  5. Great article series! Could you guys post the source when it’s complete?

  6. Permalink to comment#

    I really appreciate this tutorial :D

  7. Permalink to comment#

    Very nice and well explained tutorial… thumbs up!

  8. Q_the_Novice
    Permalink to comment#

    I’m still a little wet behind the ears as developer but i’m sure this series of tutorials will leave that spot really dry.

  9. Davide
    Permalink to comment#

    Hoping in a great .pdf at the end ;)

  10. fiefscent
    Permalink to comment#

    Thanks for the great tutorial. It’s great to not only learn the code, but also the thought process behind the initial design, etc. I’d love to get a free copy of your book “PHP for Absolute Beginners”. If it’s anywhere as informative and useful as this tutorial it will be a very valuable resource for me.

  11. Jesus
    Permalink to comment#

    great tutorial.. good work.

  12. Thats Cool!!!…

    Usefull, good performance and easy to understand it.
    Thank you guys, you did a very good tutorial.

    congratulations!

  13. David Wheeler
    Permalink to comment#

    This tutorial is really great. Thanks for doing it!

    I notice that you use a series of if/elses in the nextColor function. Is there a reason you didn’t go with a switch statement, or is it just a matter of preference?

Leave a Comment

Current day month ye@r *

*May or may not contain any actual "CSS" or "Tricks".