cs304 logo

Class on Ajax and JSON

The Three-Tier Architecture

Last week, we learned about MySQL and the database back-end. We also learned about ER diagrams. We also learned about HTML and HTML forms. (We also learned a little CSS.) On Tuesday we learned about JavaScript and jQuery.

Today, we'll dynamically update the page by interacting with the server using Ajax.

Next week, we'll learn PHP, the first kind of middle-ware that we'll be learning, and we'll finally have the entire process complete.

Goal

By the end of today, you will be able to:

Plan

  1. We'll take a minute to talk about hwk1 and hwk2.
  2. We'll review what we learned in the reading, but in a few chunks, deferring templating and event bubbling.
  3. I'll answer some of your questions from the Sakai quiz now, and the rest later, interspersed with the review of the material.
  4. We'll do three exercises comprising the goals for the day.
  5. We'll talk briefly about hwk3, due next Wednesday.

BiCoastal from Hwk1

People did really well on hwk1 on SQL. You can pick up solutions today.

Here's the solution to the bicoastal query using join:
select name,ma.city as 'MA city',ca.city as 'CA city'
from contact
inner join address as ma using (id)
inner join address as ca using (id)
where ma.state = 'MA' AND ca.state = 'CA';

The insight to this is that you need a join (cross product) of two address tables in order to check for both addresses at once.

Another solution is to use subqueries to get lists of CA and MA people:

select name,city,state
from contact inner join address using (id)
where id in (select id from address where state='MA')
and id in (select id from address where state='CA');

I'll copy/paste those into a MySQL shell so you can see them in action.

Reading Review

Example 1: Submitting a Bleat and Formatting the Response

Here's one of the final examples from the reading, but slightly re-factored: ex1.html. We'll look at the HTML, first. Notice the empty elements.

<p id="response"></p>

<p>
<form method="get" action="/~cs304/php/bleats/insert-bleat.php">
  <p>bleat text:
  <p><textarea name="bleat" rows=3 columns=60></textarea>
  <p><input type="submit" value="add bleat conventional">
  <p><input id="ajax_button" type="button" value="add bleat ajax">
</form>

<button id="refresh_button" type="button">refresh list</button>

<div id="bleats"></div>

The code divides into four reasonably sized chunks.

This function puts the timestamp at the top and fades it out. This is unchanged from the reading.

function reportSuccess () {
    var now = new Date();
    console.log('success at '+now);
    $("#response").text('saved at '
         +now.toLocaleString()).show();
    setTimeout(function () { 
                  $("#response").fadeOut('slow');
               },
               3000);
}

This function formats an array of bleats. I modified it to set the global and also to report success, using the previous function.

var global_bleats;

function showBleats(bleats) {
    global_bleats = bleats;
    var i, len = bleats.length;
    var listElt = $("<ul>");
    for( i = 0; i < len; i++ ) {
        var bleat = bleats[i];
        $("<li>")
             .text(bleat.bid+": ("+bleat.likes+") "+bleat.bleat)
             .appendTo(listElt);
    }
    $("#bleats").empty().append(listElt);
    reportSuccess();                    
}

This function handles form submissions Ajax-style, using the modified showBleats function.

$("#ajax_button").click(function () {
    $.post("/~cs304/php/bleats/insert-bleat-ajax.php",
          $("form").serialize(),
          showBleats,
          "json"); // end of .post args
});  // end of .click call

This function loads a list of bleats when page loads. It's unchanged from the reading.

function loadBleats() {
    $.post("/~cs304/php/bleats/list-bleats-ajax.php",
           showBleats,"json");
}
loadBleats();                    

Why aren't there parens after showBleats but there are after serialize()?

The parens cause the function to be invoked now and passes the return value to the .post() method. Instead, we want to pass the function itself, as a first-class object.

Imagine your friend wants some ice. You have an icemaker. You can either give her some ice, or you can give her the icemaker, and she can run it whenever she wants some ice:

var ice = iceMaker();
$.post(-, -, iceMaker(), 0 );        
$.post(-, -, iceMaker, 0 );

What questions do you have so far?

Exercise 1: Button to Refresh the List

Suppose we want to add a feature where the user can refresh the list (reload the bleats) by clicking a button. This could be useful if they are composing a message but they want to see what new bleats have come in.

Write some code to implement this feature. You can use the ex1.html as your starting point, which has the HTML for the button. Caution: this exercise is easier than you might think, so if you find yourself writing a lot of code, stop and think again.

Here's my solution:

Since the loadBleats() function already does what we need, all we have to do is attach it to the button.

$("#refresh_button").click(loadBleats);

ex1-solution.html

Templating

Another thing we learned from the reading is the idea of templating. This allows us to

Here's the example from the reading. Notice how we don't need to know the structure of the HTML; we only need to know the names of the things we are cloning and finding.

function displayFriends() {
    var i;
    // this element is "off-stage" for now
    var people = $('<div>').addClass('people');
    for( i = 0; i < friends.length; i++ ) {
        var item = friends[i];
        var clone = $('#person-template > .person').clone();
        clone.find('.name').text(item.name);
        clone.find('.house').text(item.house);
        clone.find('.pet').text(item.pet);
        clone.find('.address').text(item.address);
        people.append(clone);
    }
    // finally add everything to the document all at once
    $('#person-container').append(people);
}

Let's go back to the quiz questions. What other questions do you have?

Let's use these ideas to improve the markup in our bleat display. We want the new format to look like this:

  • #123 ( likes):

    Your bleat here. It might be so very long that it wraps around, which allows us to show off the floating info and the margin on the text element.

  • The CSS magic isn't important here, but the HTML is:

    You could write a combination of jQuery and literal strings to create that code, interspersed with data from the bleat object, but it would be:

    Let's let HTML do what it's good at, and just use jQuery to clone and modify this.

    Exercise 2: Using Templates to Format Bleats

    Write a function named formatBleats that serves the same purpose as showBleats except it uses templating. You can use the ex2.html as your starting point, which has the template and CSS code. This exercise does have a lot of coding in it; I suggest using the displayFriends() and showBleats functions as starting points. You can draft your function in the following textarea, and then copy/paste it into the ex2.html file.

    You can use the global_bleats variable for your testing.

    Here's my solution:

    function formatBleats(bleats) {
        var i, len = bleats.length;
        var listElt = $("<ul>");
        for( i = 0; i < len; i++ ) {
            var bleat = bleats[i];
            var clone = $("#bleat_template .bleat").clone();
            clone.find(".number").text(bleat.bid);
            clone.find(".likes").text(bleat.likes);
            clone.find(".text").text(bleat.bleat);
            clone.attr('data-bid',bleat.bid);           // remember this!!
            clone.appendTo(listElt);
        }
        $("#bleats").empty().append(listElt);
        reportSuccess();                    
    }

    ex2-solution.html has it fully in action. It's invoked in three different places.

    Liking, Events and Event Bubbling

    In the reading, we also learned how to add a like event handler on an ancestor of the bleats, allowing the click events to bubble up to it. A quick recap from the reading.

    liking a bleat via event bubbling to an event handler
    1. The user clicks on a bleat (an LI). That's the event target.
    2. The click bubbles up
    3. The event handler at the DIV handles it

    Here's the working solution: ajax5.html.

    Here's the code to like a bleat, given its ID. Note that this is updated to use the new formatBleats() function. Remarkably brief, given all the work it's doing!

    function likeBleat(bid) {
        $.post("/~cs304/php/bleats/like-bleat.php",
               {"bid": bid},
               formatBleats,
              "json"); // end of .post args
    }

    Here's the event handler that does all the hard work:

    $("#bleats").click(
        function (event) {
            var bid = $(event.target).attr('data-bid');
            console.log("clicked on BID "+bid);
            likeBleat(bid);
    });

    Deceptively concise:

    As concise as that code is, it would be easy to turn it into one long line of jQuery, like this:

    $("#bleats").click(function (event) { likeBleat($(event.target).attr('data-bid')); });

    But look at the frightening blizzard of punctuation at the end!

    Let's go back to the quiz questions. What other questions do you have?

    Events and Event Bubbling

    Let's see whether liking still works in our solution to the second exercise: ex2-solution.html

    It doesn't! Why not?

    Because the target is now probably the P or something else inside the LI. That is, the target is some descendant of the LI.

    How could we modify our event handler to deal with this problem?

    Work our way up the tree from the event.target until we get to the LI, then read the data-bid off that element.

    It might help to know that the jQuery .closest() method goes up the tree, returning the first ancestor that satisfies the selector.

    Exercise 3: Fixing the Event Handler

    Starting from ex3.html, and the following code for the likes event handler, fix the code and test it.

    Note that this does not require a lot of coding!

    Here's my solution:

    The key is to realize what we're looking for. We could just look for the LI or an element of class .bleat. I combined them, for clarity. However, that makes the code a bit more brittle, because if either aspect of the structure changes, this method finds the empty set, and liking fails again.

    $("#bleats").click(
          function (event) {
              var bid = $(event.target)
                           .closest("LI.bleat")
                           .attr('data-bid');
              console.log("clicked on BID "+bid);
              likeBleat(bid);
     });
    

    Here it is in action: ex3-solution.html.

    Homework 3

    We'll take a minute to look at homework 3 on Ajax

    Summary

    We've come a long way! We learned