A fun blog post popped up yesterday in which John Nunemaker ported a Quicksilver-style Live Search to jQuery. Taking a look at his code, I decided to have a little fun and re-port it to jQuery - trying to use the functional style that jQuery promotes. I think the end result is quite simple and elegant.
The final code - compare with John's port:
list = jQuery(list);
if ( list.length ) {
var rows = list.children('li'),
cache = rows.map(function(){
return this.innerHTML.toLowerCase();
});
this
.keyup(filter).keyup()
.parents('form').submit(function(){
return false;
});
}
return this;
function filter(){
var term = jQuery.trim( jQuery(this).val().toLowerCase() ), scores = [];
if ( !term ) {
rows.show();
} else {
rows.hide();
cache.each(function(i){
var score = this.score(term);
if (score > 0) { scores.push([score, i]); }
});
jQuery.each(scores.sort(function(a, b){return b[0] - a[0];}), function(){
jQuery(rows[ this[1] ]).show();
});
}
}
};
A couple points to note:
- .liveUpdate() no longer takes an element ID - it now accepts any jQuery selector (this is the only notable API change that I made).
- All state is stored in simple variables and accessed via closures, as opposed to as properties of an instance object.
- Only one function is used - and that's stored away within the function itself (greatly simplifying the resulting code).
- DOM queries are only done once and cached up front.
- .map() is used to simplify the creation of new arrays of information.




John Nunemaker (July 8, 2008 at 8:34 am)
Sweet action! It's helpful to see your implementation as I'm relatively new to jQuery.
Chris D (July 8, 2008 at 8:44 am)
It's interesting to see one mans use of jQuery in contrast to the supposed 'canon' way of doing things.
I know the changes are somewhat drastic, but it'd probably be useful to jQuery developers if you went through the changes you made a little more fine-grained, and why. (particularly, it might even be better to go through Mr. Nunemaker's code and explain why how he did it is -not- the best way, if it isn't.)
John Resig (July 8, 2008 at 9:14 am)
@John: Great script, I really like your work - and I'm glad you enjoyed my tweaking!
@Chris D: I think if/when I do this again I'll record myself doing the changes - and do voiceover commentary. A lot of the changes are just automatic (seeing queries being done multiple times - reducing down to a single call) whereas some are more complex. I ended up spending most of my time trying to figure out how to optimize the scores cache/sort code - but in the end just gave up and decided what was there was best.
bill (July 8, 2008 at 9:30 am)
@John N. - this style of search is very cool. Thanks.
@Chris D. - yes yes.
@John R. - I've seen you do this several times: take someone else's idea wrapped in a jQuery body, tear it down, then rebuild it with a jQuery heart and soul. It's fascinating but sometimes baffling. A recording and/or annotated reworking would be very helpful. Thank you.
Ben (July 8, 2008 at 9:35 am)
I second Chris D. Can you elaborate on "A couple points to note"?
Graham Mitchell (July 8, 2008 at 9:39 am)
Just one problem for me; it's case-sensitive so that if I type a capital "T", all the entries go away. Maybe that's intended, but it was a little surprising for someone who's never used Silverlight.
John Nunemaker (July 8, 2008 at 10:13 am)
@Graham - That is just a missing lower case in John's script. This line:
<code>var term = jQuery.trim( jQuery(this).val() ), scores = [];</code>
should be
<code>var term = jQuery.trim( jQuery(this).val().toLowerCase() ), scores = [];</code>
I think.
Joan Piedra (July 8, 2008 at 10:41 am)
This looks great, I went through the code and understood pretty much everything, I saw you forgot to reuse the toLowerCase() so they all match the same.
But I didn't understand this line, is the "score" method a native javascript method? If so, what does it do?
var score = this.score(term);
Great work, I loved to compare both codes, really impressive.
Also I finally understood what $.map was used for.
Thanks,
Joan
Steve Streza (July 8, 2008 at 11:02 am)
Fantastic! I was going to have to write one of these in about a week for work, very glad I don't have to now. :)
John Resig (July 8, 2008 at 11:12 am)
@Bill, Ben: I'll see if I have some extra time to elaborate or possibly record a new one. I just got a new microphone so I think that'll help with the quality, as well.
@Graham, John: Thanks for the catch, I've adjusted my code.
@Joan: The score method comes from the Quicksilver library provided on the page.
Max (July 8, 2008 at 11:53 am)
KPAX (http://www.vimeo.com/1098656) did this too and uses jQuery.
Max (July 8, 2008 at 11:54 am)
PS. Your implementation is excellent. Thanks for this, jquery and processing!
Zach (July 8, 2008 at 1:49 pm)
Vowel-less acronyms like CSS or RSS give a good number of false positives with Quicksilver (by nature), but still, this is schhhweet.
Bertrand Le Roy (July 8, 2008 at 2:41 pm)
:) I scratched my head for a few seconds trying to understand what this has to do with Live Search:
http://en.wikipedia.org/wiki/Live_Search
This is confusing. It's more like filtering or auto-complete. Nice anyway.
Thomas Peklak (July 8, 2008 at 3:58 pm)
Very elegant solution. About a year ago I tried to solve a similar problem with a much larger list (about 1000 entries). I ran into some performance issues back then, that I can see in your solution, too.
* you do not cache, the currently visible list items. so you always hide the whole list on every keyup. I only hide the entries that do not match.
* showing and hiding through show() and hide() was slower than adding and removing a class attribute with the same effect. maybe this has changed in the current jQuery version
* I ran into performance issues, when the user typed fast - the gui freezes a bit - , so I used a timeout to wait until the user finished typing
But still, I'm always fascinated on how you do things the 'jQuery way'
website design (July 8, 2008 at 4:10 pm)
That's why dad named you Joe Dirt instead of Nunemaker!!
Steve Smith (July 8, 2008 at 5:15 pm)
This is very well done! Quick question, as I'm working my way through this code, it would appear that your are sorting the resulting scores, but not actually sorting the list items that hold the data... you're simply showing them on in the order they were scored. It could be that I'm just reading it wrong.
Either way, very neat to see your take on this. Thanks!
graffiti (July 8, 2008 at 7:07 pm)
nice but type "song" and see what happens. It seems these results have nothing relevant with the initial query. Is it really accesible ?
Chad Crowell (July 9, 2008 at 2:19 am)
Sorry for the lame question, but can you post the JQ call to this script? I tried John N's call and it didn't work with this version.
Howard Katz (July 9, 2008 at 8:18 am)
@JohnR and @JohnN: very nice!
If you want to tighten up a bit more, why not remove the <form> element (and corresponding submit() code)? The <input> field works just fine w/out having to place it inside a dummy form, no? Or am I missing something?
@JohnR: I too am looking forward to seeing the video. Should I keep an eye out on Rotten Tomatoes? :-)
Howard
JoeD Fan (July 9, 2008 at 8:24 am)
@website design: Awesome!
Bill T (July 9, 2008 at 10:35 am)
Being new to jquery, and having working in javascript for a number of years, I'm baffled by this line:
var term = jQuery.trim( jQuery(this).val().toLowerCase() ), scores = [];From what I understand term is set to a trimmed/lowercased version of whatever the value of this is, but what's this
, scores = []doing tagged on to the end of it?The rest of it makes perfect sense! I think...
John Nunemaker (July 9, 2008 at 1:13 pm)
@Howard - Well normally I would keep the form element and have it submit to an actualy search and just override that functionality with js (thus the submit stuff).
@Steve - Yeah it's not sorting the list elements. My version isn't either. I can't remember why I didn't do it. Probably pure laziness. :)
Eric Martin (July 9, 2008 at 1:24 pm)
@Bill T - that is just another way of writing:
var term = jQuery.trim( jQuery(this).val().toLowerCase() );var scores = [];
For example, instead of:
var a = "something";var b = [];
var c = "something else";
var d = [1, 2, 3];
You can write it as:
var a = "something", b = [], c = "something else", d = [1, 2, 3];Daniel (July 9, 2008 at 5:42 pm)
Nice, I've seen this used for months now on dbelement.com in stripr and recently in reader.
Max (July 10, 2008 at 12:36 am)
This how it was/is done in kpax:
function si() {
var items=new Array();
items=items.concat($('#search').attr('value').toLowerCase().replace(' ','\w+').split(','));
$('.myitem').each(function(){
var show=true;
for(i=0; i
and
The input is id="search" and items are class="myitem". This ignores repeated spaces using regular expressions, is case insensitive and selects items based on their innerHTML when you type in the "search" input. You can search for different keywords (AND) by using ",".
Max (July 10, 2008 at 12:38 am)
Please remove my previous post. It did not escaped properly. sorry sorry.
Marc (July 10, 2008 at 2:44 pm)
Nice, but can you explain why are there 2 keyup events ? I don't understand -.-
.keyup(filter).keyup()
Ronnie (July 11, 2008 at 10:03 am)
So I'm a real noob. But is the difference between the codes that you don't have to rel to the quicksilver.js in your code?
Also, I looked at Johns example and wondered how you hide the list from the beginning if you don't want to show it to the users? I mean, if you want it more like a search engine...
Pierre (July 14, 2008 at 1:01 am)
Ronnie: style="display:none" and then .show() it if you want to? :D
Ronnie (July 14, 2008 at 7:47 am)
Thanks Pierre! But where do I put the code??
Ronnie (July 14, 2008 at 7:59 am)
And I must add that I'm currently using the silverlight version, but I haven't had any luck finding a solution on that site.
John Nunemaker (July 15, 2008 at 9:29 am)
@Marc - The first adds an observer that will fire whenever keyup happens and the second fires it initially so the search runs on page load if there is anything in the text field.
Marc (July 16, 2008 at 2:42 pm)
@John - Ok, thank you
Shadi Almosri (July 16, 2008 at 11:18 pm)
Fantastic function this, but i was wondering if you can help me manipulate it in someway.
I am "listing" a whole load of items in a div's, each "item" is in the div class "eventItem" and the actual filter would ideally look at the tag inside the "eventinfo" div (or even better, anything in h3,h4,h5.
Here is an a sample of how the items are displayed:
<div class="eventItem">
<div class="eventDate">
<p class="month">lug</p>
<p class="day">26</p>
<p class="dayName">sab</p>
</div>
<div class="eventInfo">
<h3>1. FC Köln </h3>
<h4>RheinEnergie Stadion</h4>
<h5>more dates</h5>
<span>trovare i biglietti</span> </div>
<br class="clearfloat" />
</div>
<div class="eventItem">
<div class="eventDate">
<p class="month">ago</p>
<p class="day">12</p>
<p class="dayName">mar</p>
</div>
<div class="eventInfo">
<h3>Eintracht Frankfurt </h3>
<h4>Commerzbank Arena</h4>
<h5>more dates</h5>
<span>trovare i biglietti</span> </div>
<br class="clearfloat" />
</div>
<div class="eventItem">
<div class="eventDate">
<p class="month">ago</p>
<p class="day">15</p>
<p class="dayName">ven</p>
</div>
<div class="eventInfo">
<h3>Bayern Monaco </h3>
<h4>Allianz Arena</h4>
<h5>more dates</h5>
<span>trovare i biglietti</span> </div>
<br class="clearfloat" />
</div>
Would i be able to replicate the functionality on this? and if so, how? :)
Thanks alot in advance!!
Ivan (July 17, 2008 at 5:06 am)
So, I'm a total noob to jquery and didn't have any contact with JavaScript for 2 years. So I missed out a lot.
Here's my problem:
I have 1 search field and below is a table with 6 columns and several rows. It's a company phone book. The data will be coming from a MS SQL Server.
Is it possible to filter this phonebook with this script? If's so what do I have to change in the script and what do I have to add to the html or php file?
Currently I've downloaded the jquery library and the livesearch script and loaded them into my html file. Also I change the "li" to "tr", since I will be searching for rows, I think?
If anyone is so nice to get in contact with me, I'd be very happy!
Adam (August 13, 2008 at 4:20 pm)
I have gotten this up and running but I am running into a problem because I made the list into links and the filter now recognizes the "a" and "href" as part of the text that it searches. How do I fix this?