183 lines
5.4 KiB
JavaScript
183 lines
5.4 KiB
JavaScript
var JSONSearch = function(options) {
|
|
|
|
this.default_options = {
|
|
fields: {},
|
|
ranks: {},
|
|
limit: null,
|
|
offset: 0,
|
|
case_sensitive: false
|
|
};
|
|
|
|
this.patterns = {
|
|
infix: '.*$token.*',
|
|
prefix: '^$token.*',
|
|
exact: '^$token$',
|
|
word: '\\b$token\\b',
|
|
word_prefix: '\\b$token.*'
|
|
};
|
|
|
|
this.query_string = '';
|
|
this.query_function = "1&&function(object) { var hits = 0, ranks_array = []; #{query_string} if (hits > 0) { return [(ranks_array.sort()[ranks_array.length - 1] || 0), hits, object]; } }";
|
|
|
|
this.initialize(options);
|
|
};
|
|
|
|
JSONSearch.InstanceMethods = {
|
|
initialize: function(options) {
|
|
this.options = {};
|
|
for(var property in this.default_options) {
|
|
this.options[property] = this.default_options[property]
|
|
};
|
|
for(var property in options) {
|
|
this.options[property] = options[property];
|
|
}
|
|
this.setAttributes(options);
|
|
this.buildMatcherTemplates();
|
|
this.buildQueryString();
|
|
},
|
|
|
|
setAttributes: function() {
|
|
var args = ['fields', 'limit', 'offset', 'case_sensitive'];
|
|
for(var i = 0; i < args.length; i++) {
|
|
this[args[i]] = this.options[args[i]];
|
|
};
|
|
this.ranks = this.getRanks();
|
|
this.fields_ordered_by_rank = this.fieldsOrderedByRank();
|
|
},
|
|
|
|
buildMatcherTemplates: function() {
|
|
for(var property in this.patterns) {
|
|
this[property + '_matcher'] = 'if(/' + this.patterns[property] + '/#{regex_options}.test(object["#{name}"])){hits++;ranks_array.push(ranks["#{name}"]);}';
|
|
};
|
|
},
|
|
|
|
getRanks: function(object) {
|
|
var ranks = {};
|
|
for(var property in this.fields) {
|
|
ranks[property] = (this.options.ranks && this.options.ranks[property] || 0);
|
|
};
|
|
return ranks;
|
|
},
|
|
|
|
// TODO if ranks are all 0 might just use the format order if supplied
|
|
fieldsOrderedByRank: function() {
|
|
var fields_ordered_by_rank = [];
|
|
for(var property in this.ranks){
|
|
fields_ordered_by_rank.push([this.ranks['property'], property]);
|
|
};
|
|
fields_ordered_by_rank = fields_ordered_by_rank.sort().reverse();
|
|
for(var i = 0; i < fields_ordered_by_rank.length; i++) {
|
|
fields_ordered_by_rank[i] = fields_ordered_by_rank[i][1];
|
|
}
|
|
return fields_ordered_by_rank;
|
|
},
|
|
|
|
buildQueryString: function() {
|
|
var query_string_array = [], field;
|
|
for(var i = 0; i < this.fields_ordered_by_rank.length; i++) {
|
|
field = this.fields_ordered_by_rank[i];
|
|
query_string_array.push(this.buildMatcher(field, this.fields[field]));
|
|
}
|
|
this.query_string = query_string_array.join('');
|
|
},
|
|
|
|
buildMatcher: function(name, pattern) {
|
|
return this.subMatcher(this[pattern + '_matcher'], { name: name, regex_options: this.getRegexOptions() });
|
|
},
|
|
|
|
subMatcher: function(matcher, object) {
|
|
var subbed_matcher = matcher;
|
|
for(var property in object) {
|
|
subbed_matcher = subbed_matcher.replace('#{' + property + '}', object[property], 'g')
|
|
}
|
|
return subbed_matcher;
|
|
},
|
|
|
|
subQueryString: function(token) {
|
|
return this.query_string.replace(/\$token/g, this.regexEscape(token));
|
|
},
|
|
|
|
subQueryFunction: function(object) {
|
|
var subbed_query_function;
|
|
for(var property in object) {
|
|
subbed_query_function = this.query_function.replace('#{' + property + '}', object[property], 'g')
|
|
}
|
|
return subbed_query_function;
|
|
},
|
|
|
|
//TODO add an options object to pass limit/offset.
|
|
getResults: function(token, object) {
|
|
object = this.evalJSON(object);
|
|
if (!(object instanceof Array)) {
|
|
object = [object];
|
|
}
|
|
var results, subbed_query_string = this.subQueryString(token);
|
|
results = this.getFilteredResults(subbed_query_string, object);
|
|
results = this.sortResults(results);
|
|
results = this.limitResults(results);
|
|
return this.cleanResults(results);
|
|
},
|
|
|
|
getFilteredResults: function(query_string, array) {
|
|
var results = [], ranks = this.ranks, result, len = (array.length), query_string = (query_string || '');
|
|
var query_function = eval(this.subQueryFunction({ query_string: query_string }));
|
|
|
|
for(var i = 0; i < len; ++i) {
|
|
if(result = query_function(array[i])) {
|
|
results.push(result);
|
|
}
|
|
}
|
|
return results;
|
|
},
|
|
|
|
sortResults: function(results) {
|
|
return results.sort().reverse();
|
|
},
|
|
|
|
limitResults: function(results) {
|
|
if (this.limit) {
|
|
return results.slice(this.offset, (this.limit + this.offset));
|
|
} else if (this.offset > 0) {
|
|
return results.slice(this.offset, (results.length));
|
|
} else {
|
|
return results;
|
|
}
|
|
},
|
|
|
|
cleanResults: function(results) {
|
|
var clean_results = []; len = (results.length);
|
|
for(var i = 0; i < len; ++i) {
|
|
clean_results.push(results[i][2]);
|
|
}
|
|
return clean_results;
|
|
},
|
|
|
|
evalJSON: function(json) {
|
|
if (typeof json == 'string') {
|
|
try {
|
|
json = eval('(' + json + ')');
|
|
} catch(e) {
|
|
throw new SyntaxError('Badly formed JSON string');
|
|
}
|
|
}
|
|
return json;
|
|
},
|
|
|
|
getRegex: function(token, pattern) {
|
|
return new RegExp(this.patterns[pattern].replace(/\$token/, this.regexEscape(token)), this.getRegexOptions());
|
|
},
|
|
|
|
getRegexOptions: function() {
|
|
return (this.case_sensitive && '' || 'i');
|
|
},
|
|
|
|
regexEscape: function(string) {
|
|
return String(string).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
|
|
}
|
|
}
|
|
|
|
for(var property in JSONSearch.InstanceMethods) {
|
|
JSONSearch.prototype[property] = JSONSearch.InstanceMethods[property]
|
|
}
|
|
delete JSONSearch.InstanceMethods;
|