One of the best features of jQuery is its extensive collection of Document Object Model (DOM) methods. They make dynamically updating the DOM easier than ever. But what makes them really great is their ability to be applied to multiple objects at once (thanks to CSS selectors) and modify multiple object attributes within one function call. Together, these features go a long way towards explaining jQuery’s dominance in this area. In today’s article, we’ll explore how to use jQuery’s DOM functions to perform batch operations.
Modifying Image Paths
A common question of web developers is how to change the src property of all the images in a page without iterating over every one. Thanks to jQuery’s versatile attr() function, you don’t have to. It’s a member of the jQuery DOM object, which means that it can be applied to both single or multiple elements. A lot of the time, selecting elements using anything other than by their unique ID will retrieve multiple elements. In the case of images, the $(“img”) selector will return every image on the page.
To derive a new src value based on the existing one it’s best to use the signature that accepts the attribute name and a function. That gives us access to the current value so that we may perform operations with it. Here’s an example music page that contains relative paths to the image sources. This is often the result of reading the data from either a feed or from another file via Ajax.
<h1>Music</h1> <p>Dream Police</p> <img alt="Dream Police" src="@/Albums/_albums/12/img1-sm.jpg" /> <p>Ballroom Blitz</p> <img alt="Ballroom Blitz" src="@/Albums/_albums/11/img1-sm.jpg" /> <p>Synchronicity II</p> <img alt="Synchronicity II" src="@/Albums/_albums/10/img1-sm.jpg" /> <p>Knightfall</p> <img alt="Knightfall" src="@/Albums/_albums/8/img1-sm.jpg" />
In the document.ready event, the existing src is appended to the URL root:
var imagesRoot = 'http://robgravelle.com/'; jQuery(document).ready(function($) { $("img").attr('src', function( i, val ) { return imagesRoot + val; }); });
Setting Multiple Attributes at Once
In addition to setting the attributes of multiple elements at once, you can also set multiple attributes on each element as well. That’s done by passing an object literal to the attr() function. Each name/value pair inside the object designates the attribute and the value to set it to. Further more, values may be any kind of valid object, including functions. Here’s a modified version of the code above that also sets the width and height attributes of each image on the page:
jQuery(document).ready(function($) { $("img").attr({ src: function( i, val ) { return imagesRoot + val; }, width: 120, height: 120 }); });
Setting Element Styles
Most DOM object methods possess the same signatures so whatever works for one will probably work for another. For instance, we can apply styles to multiple elements just as we did above for attributes.
jQuery(document).ready(function($) { $("img").css({ 'border':'gray 2px solid', 'margin-top':'5px', 'margin-bottom':'5px', 'width': function( i, val ) { return (parseInt(val) * 1.5) + 'px'; }, //val includes "px" units 'height': function( i, val ) { return (parseInt(val) * 1.5) + 'px'; } }); });
Note that the attribute names within the map argument may be quoted or not. Doing so refers to the CSS names, whereas omitting the quotes denotes JavaScript naming. The former uses dashes to separate name parts while JS naming uses camelCase. Hence, the CSS “margin-top” property would be written as marginTop without quotes.
Chaining DOM function Calls
jQuery DOM methods always return the object that’s being worked on, allowing for methods to be called successively – a construct known as method chaining. For instance, here’s some code that combines both the above operations by simply invoking the call to css() directly on the results of the attr() call:
jQuery(document).ready(function($) { $("img").attr({ src: function( i, val ) { return imagesRoot + val; } }).css({ 'border':'gray 2px solid', 'margin-top':'5px', 'margin-bottom':'5px', 'width': function( i, val ) { return (parseInt(val) * 1.5) + 'px'; }, //val includes "px" units 'height': function( i, val ) { return (parseInt(val) * 1.5) + 'px'; } }); });
Appending an Array of jQuery Objects
Strangely, jQuery can be utilized to append an array of document elements or fragments to the DOM, but not an array of jQuery objects. Doing so will throw an error such as “Uncaught Error: NOT_FOUND_ERR: DOM Exception” on Chrome or “Could not convert JavaScript argument arg 0 [nsIDOMDocumentFragment.appendChild]” on Firefox.
var friends = []; friends.push( $( "
- John
" ) ); friends.push( $( "
- George
" ) ); friends.push( $( "
- Steve
" ) ); // Append the friends to the DOM. $( "ul#friendsList" ).append( friends ); //fails!
The solution, as described by Ben Nadel is to unwrap each DOM node before appending them. While that may sound like a tall order, Mr. Nadel has incorporated his solution into a function that can be added to jQuery so that it may be called just like any other native jQuery function.
jQuery.fn.appendEach = function( arrayOfWrappers ){ // Map the array of jQuery objects to an array of // raw DOM nodes. var rawArray = jQuery.map( arrayOfWrappers, function( value, index ){ // Return the unwrapped version. This will return // the underlying DOM nodes contained within each // jQuery value. return( value.get() ); }); // Add the raw DOM array to the current collection. this.append( rawArray ); // Return this reference to maintain method chaining. return( this ); };
Conclusion
jQuery’s collection of DOM methods allow you to execute the kind of dynamic page creation that could once only be achieved via server-side tehnologies. Nonetheless, if you ever find it lacking in spots, you can always retrieve the raw DOM element so that you can utilize its own properties and methods. Between the two, there are almost no DOM operations that can’t be done.