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.