function ListFilter(options) {
var filter = filter || {};
filter.init = function(options) {
// required args
filter.listContainer = options.listContainer;
filter.filterItemClass = options.filterItemClass;
// optional args
filter.inputContainer = options.inputContainer || null;
filter.inputPrompt = options.inputPrompt || 'Search list';
filter.inputClass = options.inputClass || '';
filter.filterBlockClass = options.filterBlockClass || null;
// built here
filter.filteredList = $(filter.listContainer);
filter.filterFormID = 'list-filter-form';
filter.filterFormInputID = 'list-filter-input';
filter.filterForm = '
\
\
\
';
filter.make();
return filter;
}
filter.make = function() {
filter.addFilterForm();
filter.addMatcher();
return filter;
}
filter.addFilterForm = function() {
// add filterForm to the inputContainer if present,
// otherwise insert onto page before listContainer
if (!!filter.inputContainer) {
$(filter.inputContainer).prepend(filter.filterForm);
} else {
$(filter.filterForm).insertBefore(filter.listContainer);
}
}
filter.addMatcher = function() {
// after each keystroke in filterForm input, do a case-insensitive
// search against all filterItemClass elements inside listContainer
$('#'+filter.filterFormInputID).change(function() {
filter.filterValue = $(this).val();
if (!!filter.filterValue) {
filter.match();
} else {
filter.showAllItems();
}
filter.checkNoResults();
return false;
}).keyup(function() {
$(this).change();
});
}
filter.match = function() {
// hide the list container to avoid potential repaints
filter.filteredList.css('display','none');
// perform the matching
filter.hideUnmatchedItems();
filter.showMatchedItems();
// show the list container again
filter.filteredList.css('display','block');
}
filter.hideUnmatchedItems = function() {
// hide items that don't have matching text
filter.filteredList.find(filter.filterItemClass+':not(:icontains(' + filter.filterValue + '))').css('display','none');
if (!!filter.filterBlockClass) {
filter.filteredList.find(filter.filterBlockClass+':not(:has('+filter.filterItemClass+':visible))').css('display','none');
}
}
filter.showMatchedItems = function() {
// show items that contain matching text
if (!!filter.filterBlockClass) {
filter.filteredList.find(filter.filterBlockClass+':has('+filter.filterItemClass+':icontains(' + filter.filterValue + '))').css('display','block');
}
filter.filteredList.find(filter.filterItemClass+':icontains(' + filter.filterValue + ')').css('display','block');
}
filter.showAllItems = function () {
// nothing in filter form, so make sure everything is visible
filter.filteredList.find(filter.filterBlockClass).css('display','block');
filter.filteredList.find(filter.filterItemClass).css('display','block');
}
filter.checkNoResults = function() {
// show 'no results' message if we've removed all the items
$('#filter-no-results').remove();
if ($(filter.filterItemClass+':visible').length == 0) {
filter.filteredList.append('No matching results found.
');
}
}
// ready, set, go
filter.init(options);
return filter;
}
// custom jQuery filter selector `icontains` for text matching
// http://answers.oreilly.com/topic/1055-creating-a-custom-filter-selector-with-jquery/
$.expr[':'].icontains = function(element, index, match) {
return (element.textContent || element.innerText || "").toUpperCase().indexOf(match[3].toUpperCase()) >= 0;
};