From Data to DOM

This reading supplements the reading from Chapter 11, titled Data to DOM. We know from our earlier work with jQuery that one way to manipulate the DOM is to dynamically add elements. This chapter walks us through the code to have our Coffee Order app add a checkbox item to the page (in a list of Pending Orders) every time a coffee order form is submitted.

The key concepts are dynamically constructing DOM elements and, later, calling a method while being able to specify the value of this.

Constructing DOM Elements

If we think of the document as a living tree, what we are doing is grafting a new branch, complete with twigs and leaves, onto the tree.

Your book does a very good job of describing this, with good pictures, so this section is just a short recap:

  • We will build a complete branch first, before adding it to the tree
  • We will build each node of the branch independently, adding them to the branch using jQuery's .append method to make one node a child of another.
  • We can build a simple node like this: $("<div></div>")
  • To build a node with attributes, supply a JS object literal with the attribute names and values:

$("<input></input>", {'type': 'checkbox', 'value': 'fred'})

Your book's authors do this as part of an object constructor, with the finished branch stored as an instance variable. Only later is the finished branch grafted onto the document.

Calling Methods

In the next part of the chapter, page 235, the authors modify the form submission handler to invoke two methods on two objects. Here's the code:

:::JavaScript
formHandler.addSubmitHandler(function (data) {
    myTruck.createOrder.call(myTruck, data);
    checkList.addRow.call(checkList, data);
});

They introduce the JavaScript call method, which is a way to invoke a function as a method and supply the value of this in doing so.

It's an interesting example of the use of call, but it's unnecessary. You have an object and its argument, and you know what method to run, so the code is equivalent to two ordinary method calls:

:::JavaScript
formHandler.addSubmitHandler(function (data) {
    myTruck.createOrder(data);
    checkList.addRow(data);
});

(In fact, their solution code uses the above code, rather than the code in the book.)

We will have occasion to see .call() later in the course, when we learn about object inheritance.

Searching down the tree

We have used jQuery many times to search the DOM. For example:

$("#fred")

$("[data-coffee-form=myform]")

The expressions above search the entire document, but what if you already have a jQuery wrapped set for part of the document (a subtree), and you want to search that subtree? In that case, you can use jQuery's .find() method:

$("#fred").find('[data-coffee-form=myform]')

Or, if you already had something saved in a variable:

:::JavaScript
var $elt = $("#fred");
...
$elt.find("[data-coffee-form=myform]");

Searching up the tree

Sometimes, you want to search up the tree, along your list of ancestors (but not off the list: parent, grandparent, and great-grandparent, but not uncles, great-aunts and the like). jQuery's .closest() method is good for that. For example, the following finds the closest ancestor that is a DIV:

$elt.closest('div')

Delegation

Event delegation is a powerful jQuery technique that allows you to put one event handler on an ancestor and delegate to it handling all the events of a certain kind for all of its descendants. For example, try clicking on the items on this grocery list:

  • apple
  • banana
  • chocolate

Rather than put an event handler on each item (here only three, but you should see how long my grocery lists get, especially when I'm hungry), we can put one event handler on the UL element, and say

"anytime an LI is clicked, run this function"

We do that with the following code:

:::JavaScript
$("#groceries").on('click',
    'li', // this argument is new; the descendant who delegates the click
    function (event) {
        var clickee = event.target;
        var text = $(clickee).text();
        alert("You clicked on the "+text);
});

Note that the event.target is the actual element that was clicked on.

Event Delegation and jQuery

jQuery allows us to do event delegation in an easy way. We just use the .on method that we used before, but we add an extra argument, between the event argument and the function argument. So instead of:

$(sel).on('click',
           function () { ... });

We do:

$(sel).on('click',
          'descendant selector',
           function () { ... });

Any descendant of sel that matches descendant selector will delegate its click events to the ancestor, which runs the given function.

Why use event delegation? First, it's more efficient to have one event handler than many. Second, if we are dynamically adding and removing things from the list (as we are in the CoffeeRun app), the event handling is much simpler and cleaner if we don't have to worry about adding and removing event handlers as well.

Event Delegation and THIS

Here's a slightly different grocery list, with some more structure in each item. Compare clicking on the emphasized stuff versus non-emphasized.

  • apples, specifically Honeycrisp
  • bananas, which I like but my kids don't
  • chocolate, especially dark chocolate
:::HTML
<ul id="groceries2">
   <li>apples, specifically <em>Honeycrisp</em></li>
   <li>bananas, which <em>I</em> like but my kids <em>don't</em></li>
   <li>chocolate, especially <em>dark chocolate</em></li>
</ul>

Again, we just put one event handler in the page, attached to #groceries2 but handling any click on an LI that is a descendant of #groceries2.

:::JavaScript
$("#groceries2").on('click',
    'li', // this is new
    function (event) {
        var clickee = event.target;
        var target_text = $(clickee).text();
        var this_text = $(this).text();
        if( clickee == this ) {
            alert('both clickee and this are the same: '+this_text);
        } else {
            alert('clickee is '+target_text+' while this is '+this_text);
        }
});

Note that this is the li element, while event.target is the actual element that was clicked on, which might be descendant, such as the em. Try clicking apples and also Honeycrisp.

Form Values and .val()

In Chapter 10, we learned how to serialize all of a form's values into an array of objects, and then iterate over that array to assemble them all into a single object.

That's a fine technique, but if you just want one value, there's an easier way. jQuery has a .val() method that can get the value of an input:

To get the value, just invoke .val() with no arguments:

$(selector).val()

For example:

$("[name=zip_code]").val()
$("[name=pizza_size]").val()

This method works on text input elements, select elements (drop-down menus), and textarea elements.

For radio buttons, you can use the :checked pseudo-class to get the value of the one that are checked:

$('input[type=radio][name=size]:checked').val();

You'll need to use .val() for the next assignment (Quiz)

It's also possible to set the value of an input, but we don't need that.