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:
jQuery.fn.liveUpdate = function(list){ 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?
james (September 26, 2008 at 5:14 am)
@Adam – did you get an answer? I have the same problem!
Gary Fleshman (October 5, 2008 at 7:16 am)
@Adam and James: If your links are children of the list item, try changing line 7 to:
return $('a', this)[0].innerHTML.toLowerCase();
james (October 7, 2008 at 4:55 pm)
Gary – i love you; worked like a charm my friend.
Joost (October 8, 2008 at 7:47 am)
I’m also trying to use this script, but with a table. I can’t get it to work. Can someone enlighten me? :)
william (October 18, 2008 at 7:44 pm)
um ok…. im a noob… how the hell do you actualy USE this code?
i have the John Nunemaker code working fine… but as soon as i replace his code with this it all fails… im presuming it needs to be called some how?
also is there a way to hide results till they matched?
william (October 18, 2008 at 8:00 pm)
also interested in the table solutions
Seandon Mooy (November 14, 2008 at 1:02 pm)
Thanks John, great code!
I’ve adapted this for returning results nested inside tables. This is nifty for combining with the popular jQuery “tablesorter” plugin for ultra-useful info-tables.
The trick to getting this to work with tables is assigning your “list” class (the class attached to things that we’d like indexed.) to individual s, and using the “parents” traversal in jQuery to hide/show that ‘s . (hope that made sense).
Only change to the live search code is:
-rows.show();
+rows.parents(“.row”).show();
-rows.hide();
+rows.parents(“.row”).hide();
-jQuery(rows[ this[1] ]).show();
+jQuery(rows[ this[1] ]).parents(“.row”).show();
Then simply attach the “list” class to any cells within a “.row” table row that you’d like the search to index.
Hope this helps some of you folks asking about using this on tables.
Seandon Mooy (November 14, 2008 at 1:04 pm)
HTML didn’t escape porperly in the post above. “s” = “TD tag” or “TR tag”. Hope thats enough to make my post above make sense.
Eloy (November 21, 2008 at 11:07 am)
I am trying to find a search solution for a large table that is being pulled from a csv file. I have tried using jquery.quicksearch plugin and it worked fine with a smaller csv file but this file contains about 4000 rows and 3 columns.
\n";
while (($data = fgetcsv($handle, 5000, ",")) !== FALSE) {
$row++;
if ($row == 1) {
echo "\t\n";
echo "\t\t\n";
} elseif ($row % 2 == 0 ) {
echo "\t\t\n";
} else {
echo "\t\t\n";
}
for ($c=0; $c " . $data[$c] . "\n";
}
echo "\t\t\n";
if ($row == 1) {
echo "\t\n";
echo "\t\n";
}
}
fclose($handle);
echo "\t\n";
echo "\n";
?>
Eloy (November 21, 2008 at 11:09 am)
oops…the code deal didn’t work.
I tried using the code that John Nunemaker created but I know I have to change something in the livesearch javascript to make it work…I am just not much of a programmer….
anyhelp would be much appreciate it.
Thanks
Senning (February 15, 2009 at 11:36 am)
@Chad & William, unlike John N.’s function which takes an id name as an argument, the script here takes a reference to an element. So where the original was called by:
$('#q').liveUpdate("posts").focus();
John R.’s is called by:
$('#q').liveUpdate($("#posts")).focus();
Hope that helps!
Fred (March 7, 2009 at 9:37 am)
Hi,
Thank you guys for this clever code ! I use it with a lot of entries. Over 2500 actually. And the script is then really slow.
I mean : The first time it gets to search for 1st letter I type in the search field, the answer gets rea SLOW.
I have to face to actually have a problem when with encounter a behaviour
Fred (March 7, 2009 at 9:47 am)
Sorry for previous post… I might have been too fast on the ‘Enter’ key…
My problem is that with 2500 entries, the script is too slow. The first time I type a letter in the search field, Firefox (3.0.7 and 3.1beta2) consumes 100% CPU for about 40 seconds before giving the first answer. IE 7 deals curiously smarter with this…
But it’s really slow. Is there any possibility to improve your script to deal with as much as 2500 entries ?
Or do you think I might use something different to parse a 3200 lines file (from which my script extracts the entries…) and then display the results ?
Thanks you again,
Fred.
Fred (March 8, 2009 at 1:06 pm)
Hi again,
I find a solution with this jquery live search plugin which relies on a php request on every keypress :
http://exscale.se/archives/2008/05/16/jquery-live-ajax-search-plug-in/
It’s a lot faster with hudge amount of data as PHP is faster than Javascript with parsing variables. Or at least, that’s what I understood. :-)
Bye,
Fred.
lev5c (March 13, 2009 at 11:55 am)
First of all, amazing work! But, I am having an issue searching the list item text (date, in this case) following a link. The date was searchable before making the change suggested by Gary, but I would like to fix both issues, if possible. That is, not index the a tag, href, url, etc., and still be able to search by date as shown in my example. Thanks for any help on this matter!
ps. Title searches return just fine either way :)
<ul id=”list”>
<li><a href=””>Event Title</a>May 5th 2009, @ 3:00pm</li>
</ul>
Raffaele (March 13, 2009 at 1:51 pm)
Hi John, I wanted to know if there is the possibility to replace the algorithm of quicksilver with something more simple, that only mach the keywords entered. Actually the results of a search are not very precise, sometimes the result do not contain the word you searched yet are displayed together with the correct results. I know that will be no more cool like it is now but I think that no “normal” user will user use abbreviations for a search. Sorry for my English, thanks.
Sean O (March 31, 2009 at 10:34 am)
Also see Rik Lomas’ excellent QuickSearch plugin:
http://rikrikrik.com/jquery/quicksearch/
It implements a strict character match and a few customizable options. Recently updated for jQuery 1.3 compatibility.
— SEAN O
Chad (April 4, 2009 at 8:12 am)
Anyone happen to know how to integrate this into Vbulletin forum to replace the default search?