DOM Traversal and Modifcation

The vocabulary and methods jQuery uses to search and alter live HTML pages.

Scroll down...

Content

Resources

Comments

As you're well aware by now, the first half of using jQuery is locating the objects you actually want to apply it to. Until now, we've just demonstrated locating based on CSS-like selectors like ID, class, or element type. In this lesson, we'll dig deeper into this selection process by showing you a more complex array of selectors and how to traverse the DOM tree using relative selectors.

The second half is judiciously applying jQuery's many methods to whatever you've found. In this lesson, we'll also look into the methods jQuery provides for manipulating both returned elements and the DOM from whence they came.

To really get this stuff, we recommend you play with it in your browser console while reading.

Basic Traversal

You might think "I'm just going to grab the element(s) I want and do stuff to them, what's so hard about that?" In reality, though, you'll frequently use jQuery to respond to things like click events. Because of the nature of these events (which you'll learn about in upcoming lessons), the event might register for one of the inner elements but you want to modify an outer one.

For instance, you might want to allow a User to click on a thumbnail image, causing the containing div to slide fully into view. But the User might actually click on the image, the enclosing div, the paragraph of text that goes beside it, or the H3 on top of it. Each of these registers as a different target element. Now you want to slide out the <div> and you will need to figure out how to target that specific element from amidst all the possible targets for your User's click.

This is done with DOM Traversal. The idea of this is that you've already identified an element but now you want to pivot off that element to find another one in the DOM tree. jQuery gives you lots of useful elements to do so.

Vocabulary

First, let's iron out our DOM vocabulary.

  • A Child element is nested within the Parent element.
  • Any element nested deeper is called a Descendent of the Parent.
  • As you might expect, anything the Parent is nested inside of is an Ancestor of that child.
  • The Target element will refer to whichever element we're focusing on. Everything else will be relative to this element.

We'll use these terms from now on.

Filtering

To return only a subset of the current selection, you use filtering functions:

// Before we even talk about special functions,
// observe that you can just use the standard
// batch of CSS-style selectors
// to narrow a search to within other elements
var $someChild = $("#some-id .some-child-class");

// Our target elements
var $target = $("li");

// ...but let's assume you've already made
// the parent search and saved it to a variable
// and now you want to filter within it
// NOTE: This returns a jQuery object!!!
var $someChild = $target.filter( '.dropdown');
//=> [<li class=​"dropdown">​…​</li>​]

// Filter for elements WITHOUT a selector
var $someChild = $target.not( '.dropdown' );
//=> [<li>​…​</li>​, <li>​…​</li>​, ... <li class=​"next">​…​</li>​]

// Filter for elements that CONTAIN another selector
// like a link in this case
var $someChild = $target.has( 'a' );
//=> [<li>​…​</li>​, <li class=​"dropdown">​…​</li>​, ... <li class=​"next">​…​</li>​]

Traversing

We'll look through the available options. The point isn't to memorize them now but to see what's available. You'll probably find yourself googling for the exact function as always, but knowing what's out there can help narrow your search.

Code tells it best. Each of these returns a jQuery object:

// get the first list item on the page
var $listItem = $( 'li' ).first(); // also: .last()

// get the siblings of the list item
var $siblings = $listItem.siblings();

// get the next sibling of the list item
var $nextSibling = $listItem.next(); // also: .prev()

// get the list item's parent
var $list = $listItem.parent();

// get the list items that are immediate
// children of the list
var $listItems = $list.children();

// get ALL list items in the list,
// including nested ones
var $allListItems = $list.find( 'li' );

// find all ancestors of the list item that
// have a class of "module"
var modules = $listItem.parents( '.module' );

// find the closest ancestor of the list item
// that has a class of "module"
var module = $listItem.closest( '.module' );

You can technically perform a traversal, do some modifications, and continue traversing by using a single long method chain (like a giant run-on sentence). Don't do that:

// Yikes!
$("p").first().parent().first().addClass("hi!").next();

If you plan on working with intermediate steps, save them to a variable, modify as appropriate, then keep going in a new step.

Manipulating Elements

This is where jQuery gets fun. You're probably sick of looking at traversal without actually being able to do anything to what you've found. Now we'll (finally) talk about some of the more useful functions available to you. As always, they are just convenience wrappers around normal JavaScript that you could have written yourself.

You'll work with many of jQuery's "setter" methods here. They are often identical to the "getter" versions (which just return the existing value for that attribute) but take an additional argument specifying what you actually want to set the value to.

Some of the more useful tricks are:

  • jQuery provides "toggles" for operations that have logical opposites. For instance, adding or removing a class can be done with the toggleClass method as well, which performs the opposite operation every time you run it.

Code speaks louder than words:

// Add and remove a class
$( 'div.modal' ).addClass( 'hidden' );
$( 'div.modal' ).removeClass( 'hidden' );

// Toggle class
$( 'div.modal' ).toggleClass( 'hidden' );

// Get the CSS value for a property
$( 'li' ).first().css( 'padding-left' );

// Set inline styles on ALL `<li>` tags (implicit iteration)
// NOTE: You should prefer to add/remove classes to
// achieve the same effect!!!
$( 'li' ).css( 'padding-left', '10px' );

// work with multiple attributes by passing an object
// *This is a common pattern in most jQuery methods*
$( 'li' ).css({
  'font-size': '20px',
  'padding-left': '20px'
});

// ...or just chain them together
$( 'li' )
  .css( 'font-size', '24px')
  .css( 'padding-left', '10px' );

// Get the `value` property of a form input
// Note that we're still using CSS-style selectors here,
// though you may not have filtered on "type" in a while
$( 'input[type="text"]').val();

// Change the `value` property instead
$( 'input[type="text"]' ).val( 'new value' );

// ...or dropdowns
$( 'select' ).val( '2' );

// Checkboxes have a special "property" for their "checked"
// value, which you can set either with `prop` (the right way)
// or `attr` (the old way that still works)
$( 'input[type="checkbox"]' ).prop( 'checked', 'checked' );
$( 'input[type="checkbox"]' ).attr( 'checked', 'checked' );

// In the general case, change any attribute with `attr`
$( 'a' ).attr( 'title', 'Click me!' );

// Read a data attribute from an element (e.g. `data-fun="true"`)
$( '#special-el' ).data("fun");

// Write the data attribute
$( '#special-el' ).data("fun", "true");

There are additional other methods available to you like:

  • html() – Get or set the HTML contents.
  • text() – Get or set the text contents; HTML will be stripped.
  • attr() – Get or set the value of the provided attribute.
  • width() – Get or set the width in pixels of the first element in the selection as an integer.
  • height() – Get or set the height in pixels of the first element in the selection as an integer.
  • position() – Get an object with position information for the first element in the selection, relative to its first positioned ancestor. This is a getter only.

Passing Functions

If you want to modify an attribute or property based on the existing property, you could grab the old one, do some operations, then pass it to the new one. But jQuery uses the common JavaScript pattern of callbacks -- it takes an anonymous function as an argument and then passes in the existing value. The element's value is set to whatever is returned by the function:

// Appending a URL with your site's reference
// Note that we also get the `index` value in case
// we're actually implicitly iterating through a
// whole collection and you want to use that
// Remember -- the index is the FIRST argument!!!
$( 'a' ).attr( 'href', function(index, value) {
  return value + '?reference=mysite';
});

// The same works with CSS, for instance
// Note that we need to parse out the Integer portion
// of the returned value, which will include 'px'
// before we add to it, then return it with 'px' added
// back in.  CSS units are a pain sometimes.
$( 'p' ).first().css( 'padding-left', function(i, val){
  var newVal =  parseInt(val) + 10;
  return newVal + "px";
} );

Adding and Removing Elements

We've talked about how you can create elements in memory using jQuery (e.g. $('<p>hi</p>')) and mentioned that they can be inserted into the DOM somehow. The following functions allow you to do just that, some from the perspective of the element being placed, and others from the perspective of the element you're placing near to:

$firstLi = $('ul#some-list li').first();
$lastLi = $('ul#some-list li').last();

// Take our target `<li>` and place it inside and
// just before the closing tag of the specified element
// Note that this will nest the selected element INSIDE
// the element you're appending it to
// This actually removes the element from its original
// location and places it into the new one
$firstLi.appendTo('ul#some-other-list');

// Do the same thing but from the opposite perspective
$('ul#some-other-list').append($firstLi);

// If you want to place your new item on the SAME level
// as the specified target, use `insertAfter` instead
// of `appendTo`
// Here, we'll move the first `<li>` after the last one
$firstLi.insertAfter($lastLi);

// Or, as before, the same operation can be achieved from
// the opposite perspective using just `after`
$lastLi.after($firstLi);

// You don't need to move around existing elements
// because you can create your own, as you saw before
$newLi = $('<li>This is new!</li>');
$lastLi.after($newLi);

Appending and inserting are just a couple of the ways you can insert elements on the page. As with anything jQuery, if you think you should be able to stick an element somewhere, there's probably a method for it. Filtering the documentation by "manipulation" provides a list of options.

You'll develop preferences over time as you begin to actually use these things.

Copying an Element

If you want to just insert a COPY of an element (instead of grabbing and moving the original), use clone:

$myClonedLi = $("ul#some-list li").first().clone();
$("ul#some-other-list").append($myClonedLi);

Removing Elements

The last operation you might want to do here is removing elements from the DOM.

$lastLi = $("#some-list li").last();

// Remove the last list item
// This is the nuclear option -- it also
// goes through and removes any event handlers
// or other JavaScript functions you may have
// associated with it
// This is most common.
$lastLi.remove();

// For example, set a click handler...
$lastLi.click(function(){ console.log( "Clicked!" )});

// Remove the item...
$lastLi.remove();

// Attempt to programmatically click...
$lastLi.trigger( 'click' ); // no alert!

// If you intend to return the element to its
// rightful place, use the gentler `detach`
// You might use this to remove large sections
// of the page before making wholesale changes
// and then inserting back to the DOM later
$lastLi.detach();

// And, as you might expect, you can also replace
// one element with another
$lastLi.replaceWith('<li class="new-last-li">New one!</li>');

Gotchas

  • Make sure you know if you're working with a single element or if your selector actually returned a whole collection. If it's a collection, of course, whatever operations you perform will be done to EVERY ELEMENT of that collection via implicit iteration. That's sort of like not specifying a WHERE clause in SQL.

See jQuery's Frequently Asked Questions for lots of common how-tos.

Code Review

The important bits of code from this lesson

// *** FILTERING ***

// Filter with selector specificity
var $someChild = $("#some-id .some-child-class");

// Filter within the results with `filter`
var $someChild = $("li").filter( '.dropdown');

// Filter for elements WITHOUT a selector
var $someChild = $("li").not( '.dropdown' );

// Elements that contain a certain child
var $someChild = $("li").has( 'a' );


// *** TRAVERSAL ***

// get the first list item on the page
var $listItem = $( 'li' ).first(); // also: .last()

// get the siblings of the list item
var $siblings = listItem.siblings();

// get the next sibling of the list item
var $nextSibling = listItem.next(); // also: .prev()

// get the list item's parent
var $list = listItem.parent();

// get the list items that are immediate children of the list
var $listItems = list.children();

// get ALL list items in the list, including nested ones
var $allListItems = list.find( 'li' );

// find all ancestors of the list item that have a class of "module"
var modules = listItem.parents( '.module' );

// find the closest ancestor of the list item that has a class of "module"
var module = listItem.closest( '.module' );


// *** MANIPULATION ***

// Add and remove a class
$( 'div.modal' ).addClass( 'hidden' );
$( 'div.modal' ).removeClass( 'hidden' );

// Toggle class
$( 'div.modal' ).toggleClass( 'hidden' );

// Get the CSS value for a property
$( 'li' ).first().css( 'padding-left' );

// Set inline styles on ALL `<li>` tags (implicit iteration)
$( 'li' ).css( 'padding-left', '10px' );

// work with multiple attributes by passing an object
$( 'li' ).css({
  'font-size': '20px',
  'padding-left': '20px'
});

// ...or just chain them together
$( 'li' )
  .css( 'font-size', '24px')
  .css( 'padding-left', '10px' );

// Get the `value` property of a form input
$( 'input[type="text"]').val();

// Change the `value` property instead
$( 'input[type="text"]' ).val( 'new value' );

// Checkboxes have a special "property" for "checked"
$( 'input[type="checkbox"]' ).prop( 'checked', 'checked' );

// In the general case, change any attribute with `attr`
$( 'a' ).attr( 'title', 'Click me!' );

// Use a function to modify an attribute
$( 'a' ).attr( 'href', function(index, value) {
  return value + '?reference=mysite';
});


// *** ADDING/REMOVING ***

$firstLi = $('ul#some-list li').first();
$lastLi = $('ul#some-list li').last();

// Append inside another element
$firstLi.appendTo('ul#some-other-list');

// Append another element inside of us
$('ul#some-other-list').append($firstLi);

// Add after another element (on the same level)
$firstLi.insertAfter($lastLi);

// Add another element after us
$lastLi.after($firstLi);

// Add a newly created element to the DOM
$lastLi.after($('<li>This is new!</li>'));

// Cloning an element
$myClonedLi = $("ul#some-list li").first().clone();

// Remove entirely from the DOM and kill listeners
$("#some-list li").last().remove();

// Detach temporarily with intention to re-attach
$("#some-list li").last().detach();

// Replace one element with another
$("#some-list li").last().replaceWith('<li class="new-last-li">New one!</li>');

Wrapping Up

So you can CREATE elements in the DOM, READ elements from the DOM, UPDATE element properties, and DESTROY elements in the DOM... Sounds like jQuery is awfully CRUD-dy.

These simple operations are the bread and butter of jQuery. Though it may seem a bit overwhelming to pick up so many functions at once, you'll see and use them frequently enough that they'll get stuck in your head. This process begins in the next sprint assignment...



Sign up to track your progress for free

There are ( ) additional resources for this lesson. Check them out!

Sorry, comments aren't active just yet!

Next Lesson: Building Widgets with jQuery