/**
* Copyright (c) White Whale Web Services (http://www.whitewhale.net/)
* but free for any use, with or without modification
*
* Version 1.1 (2008-10-14)
*
* Usage: $('.inputselector').quickaccess(settingsObject);
* where .inputselector selects the container for the search input (so when the element has no other function than quickaccess it isn't shown) OR the search input itself
* settingsObject may contain:
* selector : the selector for the links that are to be searched (i.e. '#linkslist a' or 'a.quickaccess')
* results : the container in which the results will be placed (default:they'll be placed in a #qa_results created immediately after the search box)
* forceSelect : when true, an autocomplete option will always be selected; disable, for instance, if you'd like the quickaccess box to also function as a typical search box (default:true)
* onSubmit(event,selected) : callback function for when the user hits the enter/return key; by default, this will take them to the selected link (args: the keypress event and the currently selected result)
* maxResults : the maximum number of results to show at any one time
* tooMany : the message to show when there are more matching results than maxResults (default: 'Keep typing...')
* noneFound : the message to show when no results are found
* topMatch : a message to be prepended to the top matching element (i.e. 'Top Match: ')
* focus : true/false; should the search element grab focus upon initialization?
*
* Example: $('#quicksearch').quickaccess({selector:'#offices li a', maxResults:10,noneFound:'Sorry, no matching links were found.');
**/
(function($){
$.fn.extend({
quickaccess : function(s) {
var self = this;
// initialize the settings with their defaults
s = $.extend({
selector : 'a.qa',
results : null,
forceSelect : true,
onSubmit : function(e,selected) { if(selected.length) { e.preventDefault(); window.location=selected.eq(0).find('a').attr('href'); } },
maxResults : 5,
tooMany : 'Keep typing...',
noneFound : 'No matches found.',
topMatch : null,
focus : true
},s);
qa = { };
if(self.is('input[type=text],textarea')) qa.search=self; // if the specified item is an input element, use it
else qa.search = self.prepend('').children().eq(0); // otherwise, add an input inside of it
if(s.results) qa.results=$(s.results).eq(0); // select the specified container
else qa.results = qa.search.after('
').next(); // otherwise, create a new container
qa.results.addClass('qa_blur').append('
'); // initialize the blurred state and append results list to results div
qa.results_list = qa.results.find('ul#qa_results_list'); // results list is the ul we just added
qa.matches = qa.links = $(s.selector).each(function() { $.data(this,'keywords',(this.innerHTML).toLowerCase().replace(/[,-\/]/g,' ').replace(/[^a-zA-Z 0-9]+/g,'')); }); // grab links, attach data to match against
qa.search.attr('autocomplete','off').keyup(function() { // on each keypress, filter the links
var query = $.trim($(this).val().toLowerCase().replace(/[,\-\/\s]+/g,' ').replace(/[^a-zA-Z 0-9\.]+/g,'')),subquery; // grab query, sanitize it
if(query==qa.lastquery) return; // do nothing if the query is unchanged
if(!query.length) {
qa.results_list.html('');
qa.results.addClass('qa_noquery');
return;
} else qa.results.removeClass('qa_noquery');
if(query.indexOf(qa.lastquery)!=0) { // if this query is NOT a subset of the last query, reinitialize the matches and search on all terms
qa.matches = qa.links;
subquery = query;
} else subquery = query.substring(query.lastIndexOf(' ')+1,query.length); // otherwise, since this query IS a subset of the last query, no need to search the last query's terms
if(query.length==0) { qa.lastquery=''; return; } // return no results if there is no query
qa.lastquery=query;
$.each(subquery.split(' '),function() { // filter the result for each word in the query
var search = this;
qa.matches = qa.matches.filter(function() { return (' ' + $.data(this,'keywords')).indexOf(' ' + search)>=0; });
});
qa.results_list.empty();
if(qa.matches.length>s.maxResults) qa.results_list.html('
'+s.tooMany+'
').parent().addClass('qa_toomany'); // if there are too many matches
else if(!qa.matches.length) qa.results_list.html('
'+s.noneFound+'
').parent().addClass('qa_nonefound'); // if there are no matches
else { // if the matches are just right
var query_exp = new RegExp('(\\b' + query.replace(/\s/g,'|\\b') +')','ig'); // for highlighting the query terms
$.each(qa.matches,function() { qa.results_list.append('
'); }); // list the match, with highlighting
qa.results.removeClass('qa_toomany').removeClass('qa_nonefound');
var top = qa.results_list.children().eq(0).attr('id','qa_topmatch').prepend(s.topMatch);
if(s.forceSelect) top.addClass('qa_selected');
}
}).keydown(function(e) { // capture special keys on keydown
switch(e.keyCode) {
case 38: // up arrow
e.preventDefault();
var selected = qa.results_list.find('.qa_selected');
selected.removeClass('qa_selected');
if(selected.prev().length) selected.prev().addClass('qa_selected');
else if(!selected.length||s.forceSelect) qa.results_list.find('li:last-child').addClass('qa_selected');
break;
case 40: // down arrow
e.preventDefault();
var selected = qa.results_list.find('.qa_selected');
selected.removeClass('qa_selected');
if(selected.next().length) selected.next().addClass('qa_selected');
else if(!selected.length||s.forceSelect) qa.results_list.children().eq(0).addClass('qa_selected');
break;
case 13: // enter/return
var selected = qa.results_list.find('.qa_selected');
s.onSubmit.apply(self,[e,selected]);
break;
}
}).focus(function() {
qa.results.removeClass('qa_blur');
}).blur(function() {
qa.results.addClass('qa_blur');
});
if(s.focus) qa.search.focus(); // put cursor in search box
qa.search.keyup(); // run search in case field is pre-populated (e.g. in Firefox)
return this; // return original element for chaining
}
});
})(jQuery);