Ajax Search Suggestions Dropdown List
January 31, 2009 – 7:00 am
I recently built a site for a client who sells records and CDs and wanted his catalogue of over ten thousand items available for purchase from his site. I used mysql and php for the database handling side of things and, naturally, i suggested a search box to make it easier for customers to find what they were looking for.
But implementing a search system when you’re dealing with band names and record titles isn’t a simple matter. I considered mysql’s fulltext search methods, but they turned out to be useless for searching through this type of text. There were two main problems. Firstly, there’s a minimum length of words that will be indexed – this is only configurable at system level and, on a shared web hosting server, changing it wasn’t a viable option. The default minimum is 4 – which excludes band names like “yes” and “who”.
Mysql also has a “stopword” list, which contains words that don’t get indexed. The stopwords list includes words like “them”, “who”, and “yes” – all names of groups which appear in the database and need to appear in search results. Again, this list is a system variable and it wasn’t viable for me to change it. So i had to give up on the idea of using fulltext searching and do it the hard way.
The “hard way” consisted of using the “SELECT ... FROM ... WHERE ... LIKE %...%
” sql construction with the search string. But that wasn’t good enough on its own as , so i also split multi-word search strings into statements for the individual words and strung them together using “AND
” operators – so the search should turn up what they were looking for even if they had the words in the wrong order.
This worked fairly well, but for some searches it threw up lots of results that weren’t really what you were looking for. For example, searching for “yes” produced results that included “Isaac Hayes” and “Eyes Adrift”. Not a particularly big deal, i guess, but when they come at the top of the list it’s a bit confusing. I could have added more code to make sure any results that contained the search string alone came at the top of the list, but it was getting fairly complicated already and i decided that a drop-down suggestion list containing relevant search terms would be a better option.
I hadn’t used ajax before – in fact, i hadn’t written much javascript at all – but i was surprised at how easy the ajax side of the job was. I decided to use JSON, rather than XML, to transport the search results from the server to the client, for two reasons. PHP has a nice function to convert an array into JSON which was neater than writing several lines of code to build the XML document “manually”. Also, JSON is a more efficient way of sending the search results – in terms of the number of bytes that it contains – which is important when you’re trying to get a feel that’s as close to realtime as possible.
Getting the user input from the input box in the search form proved to be an altogether trickier challenge. The only reason it was trickier was because i had trouble finding good documentation on the relevant DOM events. I knew i needed to use one of onKeyDown, onKeyPress, or onKeyUp in the html tag for that input, but working out which one would be best wasn’t easy. Some of documentation kicking around on the net is just plain wrong.
After a bit of experimentation, i settled on onKeyDown – as it seemed to work better for what i needed than onKeyPress. For some reason – possibly reading inaccurate documentation – i rejected onKeyUp as an option. The KeyDown event fires for more keys than the KeyPress event, which may be why i chose it – i don’t really remember.
Anyway, KeyDown fires before the current character has been written to the input field, so i wrote an input handler to construct a string from the contents of the search box plus the key that had just been pressed. This isn’t a trivial matter, as you have to take into account the cursor position, the delete and backspace keys, as well as any selection that may be in effect at the time.
I ended up with the following code to handle user input:
var keynum; /* Get key code depending on which browser it is. */ if ( keyevent.keyCode ) { keynum = keyevent.keyCode; } else if ( keyevent.which ) { keynum = keyevent.which } else if ( window.event.keycode ) { keynum = window.event.keycode; } /* Get the text that's already in the input field. */ var inputText = document.someform.searchstring.value; /* Get the value from the select box. */ var field = document.someform.field.value; /* Which key? */ switch( keynum ) { /* first - ones we ignore */ case KEY.LEFT: case KEY.RIGHT: case KEY.UP: case KEY.DOWN: case KEY.ESC: case KEY.PAGEUP: case KEY.PAGEDOWN: break; default: { /* Get selection information */ var selectionStart = getSelectionStart( document.someform.searchstring ); var selectionEnd = getSelectionEnd( document.someform.searchstring ); if (( keynum == KEY.BS ) || ( keynum == KEY.DEL )) { /* Backspace or delete - don't add it to the string, but delete whatever character(s) it relates to. */ if ( selectionStart == selectionEnd ) { /* There's no selection - just a cursor position. So delete one character - either before or after the cursor, depending on whether it's BS or DEL. */ cursorPosition = selectionStart; if ( keynum == KEY.BS ) { // backspace - delete char before cursor var searchString = inputText.slice( 0 , cursorPosition - 1 ) + inputText.slice( cursorPosition ); } else { // del - delete char after cursor var searchString = inputText.slice( 0 , cursorPosition ) + inputText.slice( cursorPosition + 1 ); } } else { /* There is a selection - delete the characters covered by it. */ var searchString = inputText.slice( 0 , selectionStart ) + inputText.slice( selectionEnd ); } } else { /* Not deleting, so add character to search string, insert it in search string, or replace selection with it. */ var searchString = inputText.slice( 0 , selectionStart ) + String.fromCharCode( keynum ) + inputText.slice( selectionEnd ); } }
[……]
/* Functions to get the selection start and end positions. If this is IE, we need to approach it in a bit of a roundabout way to find the positions. If Mozilla etc, it's simple. */ function getSelectionStart( thisElement ) { if ( thisElement.createTextRange ) { var selectionRange = document.selection.createRange().duplicate() selectionRange.moveEnd( 'character', thisElement.value.length ) if ( selectionRange.text == '' ) return thisElement.value.length return thisElement.value.lastIndexOf( selectionRange.text ) } else return thisElement.selectionStart } function getSelectionEnd( thisElement ) { if ( thisElement.createTextRange ) { var selectionRange = document.selection.createRange().duplicate() selectionRange.moveStart( 'character', -thisElement.value.length ) return selectionRange.text.length } else return thisElement.selectionEnd }
[The last two functions were pinched from Diego Perini’s script at http://javascript.nwbox.com/cursor_position – i didn’t get round to rewriting them before i scrapped this approach.]
Of course, it took a while to get all this working properly. But as soon as i had got it working i discovered that there was a much easier way to do it!
I stumbled across Peter-Paul Koch’s excellent Quirksmode site and his Event Compatibility Tables, which told me that “[the keyup] event fires after the keystroke has been processed; for instance after a character has been added to a text input.” Doh! That was what i needed to know before i wrote all that code above!
A minimal amount of experimentation told me that onKeyUp was what i should be using, so i scrapped all the code above and replaced it with:
/* Get the text from the input field. */ var searchString = searchfield.value;
which was much neater! I hadn’t wasted my time though, because i now had a good understanding of text input handling with javascript and the DOM.
There are several jQuery plugins for dropdown suggestion lists, but all the ones i looked at either seemed a bit buggy or didn’t work the way i wanted them to. Using one of them would also mean including the jQuery library in the page’s code – which adds a slight download overhead – so i decided against it. If i’d been using jQuery for anything else in the page, it probably would have been different but, as i wasn’t, writing my own code was easier and more efficient.
I was surprised at how easy it was to implement the dropdown search suggestions list. It’ll make searching easier for my client’s customers and hopefully it will help him sell more records. But i’m not sure if it will fix the problem with searching for “yes” – with short names like that, anyone who can type at a reasonable speed will have typed the whole thing into the search box before they see it in the suggestions. And unless the string comes from the suggestions, the search will be loose, rather than exact.
But Yes are crap anyway, so who cares? 😉
Leave a Reply