Showing posts with label selectors. Show all posts

jQuery: How many elements were selected

Anyone who used jQuery, starts with a CSS selector to either attach an event or apply some DOM manipulation. The power of jQuery lies in it’s CSS selector. One of the reasons why jQuery become so popular is probably in the concept shift it made to front-end development. Instead of starting your code with javascript function definitions, you start with DOM element selection and straight to your business requirements manipulating those elements. It abstracted away the problems of code structure and let you get on with your needs by selecting the objects you need.

Sometimes your code might require to identify how many elements were selected by jQuery. It might be to identify if there are any elements on the page or to use the number of elements in some other way. For example to set rowspan on your newly created table row. In this post, you will learn how to find out how many elements were selected and see some examples.

jQuery selector returns an array of elements jQuery object that has .length attribute. So the easiest way is to get the length of returned jQuery object. The size of that object will be equal to the number of selected elements. Also, you can use built in jQuery helper function called .size(). It returns the same value as .length property. So you can use .length or .size() depending on your choice, whichever makes your code more readable.

Examples

Assume we have the following HTML structure:

<div class="container">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
</div>

Here is how you get the number of selected elements and check if there are any elements on the page.

  1. Check the number of elements:

    $('.container').length;  // returns 1
    $('.item').length;       // returns 2
    
    // Same with .size()
    $('.container').size();  // returns 1
    $('.item').size();       // returns 2
  2. Check if there are any elements:

    if($('.container').size() > 0) {
      // There is an element with class "container"
    } else {
      // There is no element with class "container"
    }

Let's see another example. Consider we have the following mark-up for a shopping cart with several order lines in it.

<div class="shopping-cart">
  <div class="order-line">Product 1 – <span>$4.99</span></div>
  <div class="order-line">Product 2 – <span>$9.99</span></div>
</div>

Business requirement: Show a modal box with special offer, if user has more than 1 products in his/her shopping cart.

if($('.shopping-cart .order-line').length > 1) {
  // Show modal box with special offer
}

jQuery custom selectors with parameters

My last tutorial on how to create a custom jQuery selector showed you the basics of creating custom filters in jQuery. Now, it is time to get more serious with selectors and create more advanced jQuery selectors – custom jQuery selectors with parameters. To get an idea of what I am talking about think of :contains(someText) selector.

Anyway, let’s create our own jQuery selector that takes arguments. The basic syntax is the same:

$.expr[':'].test = function(obj, index, meta, stack){
    /* obj - is a current DOM element
       index - the current loop index in stack
       meta - meta data about your selector !!!
       stack - stack of all elements to loop
   
       Return true to include current element
       Return false to explude current element
    */
};

meta is a parameter we are interested in. meta is an array that carries an information about our selector. Here is what it looks like:

$('a:test(argument)');
//meta would carry the following info:
[
    ':test(argument)', // full selector
    'test',            // only selector
    '',                // quotes used
    'argument'         // parameters
]

$('a:test("arg1, arg2")');
//meta would carry the following info:
[
    ':test('arg1, arg2')', // full selector
    'test',                // only selector
    '"',                   // quotes used
    'arg1, arg2'           // parameters
]

Here as you can see, we can make use of the arrays fourth (meta[3]) value to get a hang of our parameters.

Creating custom jQuery selector with parameters

Now, let’s improve our selector from previous post that selects all links with non empty rel attribute and make it select any element with specified non empty attribute. Here is the code to do just that:

$.expr[':'].withAttr = function(obj, index, meta, stack){
  return ($(obj).attr(meta[3]) != '');
};

See it in action here.

Find & select all external links with jQuery

Selecting all external links and amending them in some way is probably one of the most used jQuery tutorials. By selecting all anchor links on the page and for example adding a CSS class with only one jQuery line shows how jQuery is simple and powerful. Also if you are progressively enhancing your website this is one of the trick you might use.

Actually, I had to select all external links and add an image that indicates it the other day. So, I created a custom jQuery selector called :external that makes finding external links easier for you. (Read “Custom jQuery Selectors” post to learn how to create your own custom selectors)

External links custom jQuery selector code:

// Creating custom :external selector
$.expr[':'].external = function(obj){
    return !obj.href.match(/^mailto\:/)
            && (obj.hostname != location.hostname);
};

// Add 'external' CSS class to all external links
$('a:external').addClass('external');

You can see this code in action here.

You may use the code above to:

  • Add CSS class to all external links
  • Dynamically add an image after the link to indicate that it is an external link
  • Bind a click event to track what links where clicked
  • etc.

Update: The return part was update to take into the account the mailto links as suggested in comments by Phillipp and Karl below.

How to add more items to the existing jQuery selection

There are occasions when you have already selected elements and need to add more items to it. Imagine you have selected different items and have jQuery selection object. For example:

var elms = $('#elem, .items');

Now, you need to add more new items (DOM nodes) to your section. Let’s say you want to add .otherItems to your elms selection. You could achieve it by using one of these methods:

elms.add('.otherItems');
$('.otherItems').add(elms); // The same as above

The post title for the second method would be “How to add jQuery object/selection to the existing selection”. Anyway, it’s just wording :)

How to get full html string including the selected element itself with jQuery's $.html()

Sometimes you need to get the selected element's html as well when using .html() function. To make it more clear what I mean, consider you have this HTML markup:

<div id="top">
  <div id="inner">
    Some content
  </div>
  More content
</div>

And you need to get not only <div id="inner">Some con... but <div id="top"><div id="inner">Some con...

Here is the code to get jQuery selector's HTML including its own:

var html = $('<div>').append($('#top').clone()).remove().html();

Here we are:

  1. Cloning selected div
  2. Creating a new div DOM object and appending created clone of the div
  3. Then getting the contents of wrapped div (which would give us all HTML)
  4. Finally removing the created DOM object, so it does not clutter our DOM tree.

This is a little trick you can use to select self HTML with jQuery's .html() function. But if you can you should surround your markup with some dummy div and avoid this workaround all together. This would be much faster since jQuery would not need to do all the DOM manipulations.

jQuery 1.2.6 and jQuery 1.3 class selector performance benchmark

Reading about the jQuery 1.3's new selector engine Sizzle and its speed improvements I thought I would do a performance comparison between jQuery 1.2.6 and jQuery 1.3. I was prepared for something good, but the test results blew my mind.

I had a page with one unordered list with 1000 items each with a class (class="1", class="2", etc).

Here is  are the tests and results:
console.time("testClass");
for(i=0;i<100;i++){
    $('.'+i);
}
console.timeEnd("testClass");
/**
* jQuery 1.2.6

1235 ms
1326 ms
1342 ms
=======
1301 ms

*/
/**
* jQuery 1.3

54 ms
52 ms
53 ms
=======
53 ms

*/

As you can see the new selector engine is freakishly fast :) Actually with this specific test it is 25 times fast. Taking into the consideration that class selection is one of the most used selectors, we can assume that our code will work considerably faster.

NOTE:
I have performed the same  tests with selection with id's. The result were exactly the same (9 ms). Taking into the consideration that both versions of jQuery use browser's built in getElementById() function for ID selections, there is not much one can do to beat that.

jQuery and HTML image maps

Most of us don't even use image maps anymore. But occasionally, mostly when freelancing, you will need to work with the pages that were created in mid 90's and image maps where pop stars in that era. Anyway to refresh your memory here is how HTML image map looks like:

<img src="bigImage.gif" usemap="#parts" />  
<map name="parts">  
  <area shape="rect" coords="20,6,200,60" href="http://www.boo.uz">  
  <area shape="circle" coords="100,200,50" href="http://www.google.com">  
</map>

To access HTML image map area attributes and properties use something like this:

$('area').click(function() { 
    var url = $(this).attr('href'); 
    var coords = $(this).attr('coords').split(','); 

    // Your code here 

    // To prevent default action 
  return false; 
});

This is all that I have on jQuery and image maps.

How to select innermost element with jQuery

Today I was working on new category browser UI for the project I am working. I had to select the innermost element and append some more content into it. Basically, I had an HTML like this:

<div>Outermost element 
  <div>Some Text 
    <div>Evenmore text 
      <div>Who cares anymore? 
        <div>Innermost Element</div> 
      </div> 
    </div> 
  </div> 
</div>

So I needed to select the innermost div and append another div to it. There is no jQuery selector but you can use selectors that exist to achieve this goal. The innermost element would be the last div with the only-child.

$('div:only-child:last'); 

// Change background color to gray 
$('div:only-child:last').css('background-color',"#ccc");

Improving jQuery code performance

Following jQuery performance related articles here is another tip to boost your jQuery code performance. You should use ID selections whenever you can. Because jQuery uses browser's native getElementById() function which is much faster.

Now back to JavaScript code performance testing. I have an unordered list with 1000 items. First test will have list items with class attributes and the second list will have id attributes set to its items.

console.time('test');  
for (i = 0; i < 500; i++) {  
    $('.'+i);  
}  
console.timeEnd('test'); 
// ~8204ms 

console.time('test');  
for (i = 0; i < 500; i++) {  
    $('#'+i);  
}  
console.timeEnd('test'); 
// ~32ms

As you can see selecting with element's #id is much faster. In our case 256 times (8204/32).

Caching in jQuery

What is great about jQuery is its simplicity in selecting elements. We all use it here and there, basically everywhere. This simplicity comes with its drawbacks. jQuery traverses through all elements every time we use selectors. So to boost up your jQuery application you should always cache your selections to some variable (if you find yourself using the same selection more than once). In case you are not selecting an element more than once you should not cache your selection by assigning it to some variable.

Here is an example:
var cached = $('.someElement'); 
cached.addClass('cached-element');
Here are the performance tests:
console.time('test'); 
for (i = 0; i < 1000; i++) { 
    $('.the').text(i + ' '); 
} 
console.timeEnd('test'); 
// ~260ms 

console.time('test2'); 
var the = $('.the'); 
for (i = 0; i < 1000; i++) { 
    the.text(i + ' '); 
} 
console.timeEnd('test2'); 
// ~30ms

As you can see caching increased performance by nearly 10 times.