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