blob: 186cbc611176cb46013266d2f6ec0039a187888c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function ($) {
/**
* A <i>term</i> autocomplete search box, using jQueryUI.autocomplete. This
* implementation uses Solr's facet.prefix technique. This technique benefits
* from honoring the filter query state and by being able to put words prior to
* the last one the user is typing into a filter query as well to get even more
* relevant completion suggestions.
*
* Index instructions:
* 1. Put a facet warming query into Solr's "firstSearcher" in solrconfig.xml,
* for the target field.
* 2. Use appropriate text analysis to include a tokenizer (not keyword) and do
* <i>not</i> do stemming or else you will see stems suggested. A 'light'
* stemmer may produce acceptable stems.
* 3. If you are auto-completing in a search box that would normally be using
* the dismax query parser AND your qf parameter references more than one field,
* then you might want to use a catch-all search field to autocomplete on.
*
* For large indexes, another implementation approach like the Suggester feature
* or TermsComponent might be better than a faceting approach.
*
* Other types of autocomplete (a.k.a. suggest) are "search-results",
* "query-log", and "facet-value". This widget does term autocompletion.
*
* @author David Smiley <david.w.smiley at gmail.com>
*/
AjaxSolr.AutocompleteTermWidget = AjaxSolr.AbstractTextWidget.extend(
/** @lends AjaxSolr.AutocompleteTermWidget.prototype */
{
/**
* @param {Object} attributes
* @param {String} attributes.field The Solr field to autocomplete indexed
* terms from.
* @param {Boolean} [attributes.tokenized] Whether the underlying field is
* tokenized. This component will take words before the last word
* (whitespace separated) and generate a filter query for those words, while
* only the last word will be used for facet.prefix. For field-value
* completion (on just one field) or query log completion, you would have a
* non-tokenized field to complete against. Defaults to <tt>true</tt>.
* @param {Boolean} [attributes.lowercase] Indicates whether to lowercase the
* facet.prefix value. Defaults to <tt>true</tt>.
* @param {Number} [attributes.limit] The maximum number of results to show.
* Defaults to 10.
* @param {Number} [attributes.minLength] The minimum number of characters
* required to show suggestions. Defaults to 2.
* @param {String} [attributes.servlet] The URL path that follows the solr
* webapp, for use in auto-complete queries. If not specified, the manager's
* servlet property will be used. You may prepend the servlet with a core if
* using multiple cores. It is a good idea to use a non-default one to
* differentiate these requests in server logs and Solr statistics.
*/
constructor: function (attributes) {
AjaxSolr.AutocompleteTermWidget.__super__.constructor.apply(this, arguments);
AjaxSolr.extend(this, {
field: null,
tokenized: true,
lowercase: true,
limit: 10,
minLength: 2,
servlet: null
}, attributes);
},
init: function () {
var self = this;
if (!this.field) {
throw '"field" must be set on AutocompleteTermWidget.';
}
this.servlet = this.servlet || this.manager.servlet;
$(this.target).find('input').bind('keydown', function (e) {
if (e.which == 13) {
var q = $(this).val();
if (self.set(q)) {
self.doRequest();
}
}
});
$(this.target).find('input').autocomplete({
source: function (request, response) { // note: must always call response()
// If term ends with a space:
if (request.term.charAt(request.term.length - 1).replace(/^ +/, '').replace(/ +$/, '') == '') {
response();
return;
}
var term = request.term,
facetPrefix = term, // before the last word (if we tokenize)
fq = '',
store = new AjaxSolr.ParameterStore();
store.addByValue('fq', self.manager.store.values('fq'));
if (self.tokenized) {
// Split out the last word of the term from the words before it.
var lastSpace = term.lastIndexOf(' ');
if (lastSpace > -1) {
fq = term.substring(0, lastSpace);
facetPrefix = term.substring(lastSpace + 1);
store.addByValue('fq', '{!dismax qf=' + self.field + '}' + fq);
}
}
if (self.lowercase) {
facetPrefix = facetPrefix.toLowerCase();
}
store.addByValue('facet.field', self.field);
store.addByValue('facet.limit', self.limit);
store.addByValue('facet.prefix', facetPrefix);
self.manager.executeRequest(self.servlet, 'json.nl=arrarr&q=*:*&rows=0&facet=true&facet.mincount=1&' + store.string(), function (data) {
response($.map(data.facet_counts.facet_fields[self.field], function (term) {
var q = (fq + ' ' + term[0]).replace(/^ +/, '').replace(/ +$/, '');
return {
label: q + ' (' + term[1] + ')',
value: q
}
}));
});
},
minLength: this.minLength,
select: function(event, ui) {
if (self.set(ui.item.value)) {
self.doRequest();
}
}
});
}
});
})(jQuery);