1 (function ($) { 2 3 /** 4 * A <i>term</i> autocomplete search box, using jQueryUI.autocomplete. This 5 * implementation uses Solr's facet.prefix technique. This technique benefits 6 * from honoring the filter query state and by being able to put words prior to 7 * the last one the user is typing into a filter query as well to get even more 8 * relevant completion suggestions. 9 * 10 * Index instructions: 11 * 1. Put a facet warming query into Solr's "firstSearcher" in solrconfig.xml, 12 * for the target field. 13 * 2. Use appropriate text analysis to include a tokenizer (not keyword) and do 14 * <i>not</i> do stemming or else you will see stems suggested. A 'light' 15 * stemmer may produce acceptable stems. 16 * 3. If you are auto-completing in a search box that would normally be using 17 * the dismax query parser AND your qf parameter references more than one field, 18 * then you might want to use a catch-all search field to autocomplete on. 19 * 20 * For large indexes, another implementation approach like the Suggester feature 21 * or TermsComponent might be better than a faceting approach. 22 * 23 * Other types of autocomplete (a.k.a. suggest) are "search-results", 24 * "query-log", and "facet-value". This widget does term autocompletion. 25 * 26 * @author David Smiley <david.w.smiley at gmail.com> 27 */ 28 AjaxSolr.AutocompleteTermWidget = AjaxSolr.AbstractTextWidget.extend( 29 /** @lends AjaxSolr.AutocompleteTermWidget.prototype */ 30 { 31 /** 32 * @param {Object} attributes 33 * @param {String} attributes.field The Solr field to autocomplete indexed 34 * terms from. 35 * @param {Boolean} [attributes.tokenized] Whether the underlying field is 36 * tokenized. This component will take words before the last word 37 * (whitespace separated) and generate a filter query for those words, while 38 * only the last word will be used for facet.prefix. For field-value 39 * completion (on just one field) or query log completion, you would have a 40 * non-tokenized field to complete against. Defaults to <tt>true</tt>. 41 * @param {Boolean} [attributes.lowercase] Indicates whether to lowercase the 42 * facet.prefix value. Defaults to <tt>true</tt>. 43 * @param {Number} [attributes.limit] The maximum number of results to show. 44 * Defaults to 10. 45 * @param {Number} [attributes.minLength] The minimum number of characters 46 * required to show suggestions. Defaults to 2. 47 * @param {String} [attributes.servlet] The URL path that follows the solr 48 * webapp, for use in auto-complete queries. If not specified, the manager's 49 * servlet property will be used. You may prepend the servlet with a core if 50 * using multiple cores. It is a good idea to use a non-default one to 51 * differentiate these requests in server logs and Solr statistics. 52 */ 53 constructor: function (attributes) { 54 AjaxSolr.extend(this, { 55 field: null, 56 tokenized: true, 57 lowercase: true, 58 limit: 10, 59 minLength: 2, 60 servlet: null 61 }, attributes); 62 }, 63 64 init: function () { 65 var self = this; 66 67 if (!this.field) { 68 throw '"field" must be set on AutocompleteTermWidget.'; 69 } 70 this.servlet = this.servlet || this.manager.servlet; 71 72 $(this.target).find('input').bind('keydown', function (e) { 73 if (e.which == 13) { 74 var q = $(this).val(); 75 if (self.set(q)) { 76 self.doRequest(); 77 } 78 } 79 }); 80 81 $(this.target).find('input').autocomplete({ 82 source: function (request, response) { // note: must always call response() 83 // If term ends with a space: 84 if (request.term.charAt(request.term.length - 1).replace(/^ +/, '').replace(/ +$/, '') == '') { 85 response(); 86 return; 87 } 88 89 var term = request.term, 90 facetPrefix = term, // before the last word (if we tokenize) 91 fq = '', 92 store = new AjaxSolr.ParameterStore(); 93 94 store.addByValue('fq', self.manager.store.values('fq')); 95 96 if (self.tokenized) { 97 // Split out the last word of the term from the words before it. 98 var lastSpace = term.lastIndexOf(' '); 99 if (lastSpace > -1) { 100 fq = term.substring(0, lastSpace); 101 facetPrefix = term.substring(lastSpace + 1); 102 store.addByValue('fq', '{!dismax qf=' + self.field + '}' + fq); 103 } 104 } 105 if (self.lowercase) { 106 facetPrefix = facetPrefix.toLowerCase(); 107 } 108 109 store.addByValue('facet.field', self.field); 110 store.addByValue('facet.limit', self.limit); 111 store.addByValue('facet.prefix', facetPrefix); 112 113 self.manager.executeRequest(self.servlet, 'json.nl=arrarr&q=*:*&rows=0&facet=true&facet.mincount=1&' + store.string(), function (data) { 114 response($.map(data.facet_counts.facet_fields[self.field], function (term) { 115 var q = (fq + ' ' + term[0]).replace(/^ +/, '').replace(/ +$/, ''); 116 return { 117 label: q + ' (' + term[1] + ')', 118 value: q 119 } 120 })); 121 }); 122 }, 123 minLength: this.minLength, 124 select: function(event, ui) { 125 if (self.set(ui.item.value)) { 126 self.doRequest(); 127 } 128 } 129 }); 130 } 131 }); 132 133 })(jQuery); 134