Last active
August 29, 2015 14:27
-
-
Save cullenjohnson/7062e039b5fc46e5c1b8 to your computer and use it in GitHub Desktop.
Returns a compare function to be used in JavaScript's `Array.prototype.sort()` function that finds the string match of a search term nearest to the start of the word.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** GetSearchTermRelevanceComparator | |
* Author: Cullen Johnson | |
* Github: https://github.com/cullenjohnson | |
* Website: cullenjohnson.ca | |
* | |
* Return an underscore sort comparator that can sort a list of strings based on the relevancy of the search term. | |
* Can be used to sort strings, objects with string properties, or Backbone objects with string properties. | |
* | |
* Search Priority: (Example search term: "FOO") | |
* 1. Exact match of search term ("FOO" > "super FOObar") | |
* 2. Nearest match of search term at the start of a word. ("super FOObar" > "super duper FOObar" > "kungFOO") | |
* 3. Match nearest to the start of the word. ("blablabla kungFOO" > "blablablaFOO") | |
* | |
* NOTE: When the search term contains a space, the function prioritizes the closest match, ignoring its word location. | |
* (Example: When the search term is "FOO BAR", "superFOO BAR" > "bla bla bla FOO BAR") | |
* | |
* Options: | |
* * searchTerm: the string to search for. | |
* * options | |
* caseSensitive: set to true if the search should be case sensitive. (False by default) | |
* propertyName: if the items being compared are objects, set this parameter to the name of the property to search | |
* for the term. (Leave null if the items being compared are strings.) | |
* isBackBoneObject: set to true if the items being compared are Backbone objects. (Leave null if the items being compared are strings.) | |
*/ | |
getSearchTermRelevanceComparator = function(searchTerm, options) { | |
return function(a, b) { | |
var aWordList, bWordList, i, | |
compareMatchPosition, | |
aMatchingWordIndex = 0, aMatchPosition = null, | |
bMatchingWordIndex = 0, bMatchPosition = null, | |
caseSensitive = options.caseSensitive || false, | |
propertyName = options.propertyName || null, | |
isBackboneObject = options.isBackboneObject || false; | |
if (propertyName) { | |
if (isBackboneObject) { | |
a = a.get(propertyName); | |
b = b.get(propertyName); | |
} else { | |
a = a[propertyName]; | |
b = b[propertyName]; | |
} | |
} | |
if (!caseSensitive) { | |
a = a.toUpperCase(); | |
b = b.toUpperCase(); | |
searchTerm = searchTerm.toUpperCase(); | |
} | |
// first of all, if a term does not contain the searchTerm, it is at the bottom. | |
if (a.indexOf(searchTerm) < 0 && b.indexOf(searchTerm) < 0) { | |
return 0; | |
} if (a.indexOf(searchTerm) < 0) { | |
return 1; | |
} else if (b.indexOf(searchTerm) < 0) { | |
return -1; | |
} | |
// top priority: exact match | |
if (a === searchTerm) { | |
return -1; | |
} | |
if (b === searchTerm) { | |
return 1; | |
} | |
// next priority: match at start of the word | |
// if the search term contains spaces, prioritize the match closer to the start of the entire string. | |
if (/\s/.test(searchTerm)) { | |
aMatchPosition = a.indexOf(searchTerm); | |
bMatchPosition = b.indexOf(searchTerm); | |
} else { | |
// split compared terms into collections of words. | |
aWordList = a.split(/\s+/g); | |
bWordList = b.split(/\s+/g); | |
// find the match that is closest to the start of the word for each collection of words. | |
for (i = 0; i < aWordList.length; ++i) { | |
compareMatchPosition = aWordList[i].indexOf(searchTerm); | |
if (compareMatchPosition >= 0 && (_.isNull(aMatchPosition) || compareMatchPosition < aMatchPosition )) { | |
aMatchingWordIndex = i; | |
aMatchPosition = compareMatchPosition | |
} | |
} | |
for (i = 0; i < bWordList.length; ++i) { | |
compareMatchPosition = bWordList[i].indexOf(searchTerm); | |
if (compareMatchPosition >= 0 && (_.isNull(bMatchPosition) || compareMatchPosition < bMatchPosition )) { | |
bMatchingWordIndex = i; | |
bMatchPosition = compareMatchPosition | |
} | |
} | |
} | |
// If the match is closer to the start of any word in the two terms, return that term. | |
if (aMatchPosition < bMatchPosition) { | |
return -1; | |
} else if (bMatchPosition < aMatchPosition) { | |
return 1; | |
} else { | |
// If the match is at the same position in both words, return the term with the earliest matching word. | |
if (aMatchingWordIndex < bMatchingWordIndex) { | |
return -1; | |
} else if (bMatchingWordIndex < aMatchingWordIndex) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
}; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment