Introduction
In this open-ended series I'll be showcasing a snippet of buggy jQuery code that you might encounter, explain what the problem is, and then identify how you can easily resolve the issue.
You can view my other post in this series...
The Desired Feature
We want to take the following HTML unordered list and build a JavaScript function that will determine if a specific value is present.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<ul id="numberList"> | |
<li data-value="1">One</li> | |
<li data-value="2">Two</li> | |
<li data-value="3">Three</li> | |
<li data-value="4">Four</li> | |
<li data-value="5">Five</li> | |
<li data-value="6">Six</li> | |
<li data-value="7">Seven</li> | |
<li data-value="8">Eight</li> | |
<li data-value="9">Nine</li> | |
<li data-value="10">Ten</li> | |
</ul> |
The Buggy Code
The following code snippets is our first attempt at solving the problem, but there is a subtle error. Do you see it?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function listHasNumber( $list, numberToFind ) { | |
$list.find( "li" ).each( function() { | |
if ( $( this ).data( "value" ) === numberToFind ) { | |
return true; | |
} | |
}); | |
return false; | |
} | |
var $numberList = $( "#numberList" ); | |
console.log( listHasNumber( $numberList, 0 ) ); // false | |
console.log( listHasNumber( $numberList, 1 ) ); // false | |
console.log( listHasNumber( $numberList, 5 ) ); // false | |
console.log( listHasNumber( $numberList, 10 ) ); // false | |
console.log( listHasNumber( $numberList, 15 ) ); // false |
You can view, run, and edit the above code sample from JSBin.
The results that we expected were
false, true, true, true, false
, but instead the output in the console is false, false, false, false, false
. The Underlying Problem
At the root of the problem is the special jQuery
.each()
method we are using. The .each()
method is a very handy way to iterate over the jQuery collection, however there is a special meaning to return false
within a .each()
method."We can stop the loop from within the callback function by returning false." --http://api.jquery.com/each
Upon further examination of the code it even makes further sense why it wouldn't work. Most of us are familiar that a
return
statement exists the current function, but in this case we aren't invoking the function... jQuery is! So, jQuery has control over what happens when you exit your function prematurely and it recognizes return false
to mean something special.A Solution
The solution to fix this problem is really simple and straightforward. All you really need to do is to introduce another variable
hasNumber
. If the number was found, then set the hasNumber
variable to true
and then return false;
to exit the .each()
method.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function listHasNumber( $list, numberToFind ) { | |
var hasNumber = false; | |
$list.find( "li" ).each( function() { | |
if ( $( this ).data( "value" ) === numberToFind ) { | |
hasNumber = true; | |
return false; | |
} | |
}); | |
return hasNumber; | |
} | |
var $numberList = $( "#numberList" ); | |
console.log( listHasNumber( $numberList, 0 ) ); // false | |
console.log( listHasNumber( $numberList, 1 ) ); // true | |
console.log( listHasNumber( $numberList, 5 ) ); // true | |
console.log( listHasNumber( $numberList, 10 ) ); // true | |
console.log( listHasNumber( $numberList, 15 ) ); // false |
You can view, run, and edit the above code sample from JSBin.
If you test out the code again below you'll notice that now that we are getting the expected output of
false, true, true, true, false
.An Alternate Solution
An alternate way to solve this problem would be to use another technique completely. In the following example we will use the
jQuery.grep
method. The jQuery.grep
method is defined as the following:"Finds the elements of an array which satisfy a filter function. The original array is not affected." --http://api.jquery.com/jquery.grep/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function listHasNumber( $list, numberToFind ) { | |
return !!$.grep( $list.find( "li" ).get(), function( element ) { | |
return $( element ).data( "value" ) === numberToFind; | |
}).length; | |
} | |
var $numberList = $( "#numberList" ); | |
console.log( listHasNumber( $numberList, 0 ) ); // false | |
console.log( listHasNumber( $numberList, 1 ) ); // true | |
console.log( listHasNumber( $numberList, 5 ) ); // true | |
console.log( listHasNumber( $numberList, 10 ) ); // true | |
console.log( listHasNumber( $numberList, 15 ) ); // false |
You can view, run, and edit the above code sample from JSBin.
Although the code looks shorter, it is less performant than the previous solution. Can you tell why? The code is not exiting once the number has been found, but instead goes through each array item and executes the callback function. We get the correct answer, but for a price.
Conclusion
The key concept to remember here is to be aware of the special mean of
return false;
inside of a jQuery .each()
method to exit the loop. Even if you have never made this mistake before, maybe it has opened up your eyes to the fact you can exit out of a .each()
method!Until next time...