PageSourceSearch

https://www.jhrl.com/sites/all/libraries/ajax_solr/ajax-solr.min.js?tlse4v

js jhrl.com collected 2026-10-06 13:17:44 UTC 67,575 bytes, 2,217 lines download raw bytes

1/**
2 * @namespace A unique namespace for the AJAX Solr library.
3 */
4AjaxSolr = function () {};
5
6/**
7 * @namespace Baseclass for all classes
8 * @see https://github.com/documentcloud/backbone/blob/51eed189bf4d25877be4acdf51e0a4c6039583c5/backbone.js#L243
9 */
10AjaxSolr.Class = function(attributes) {
11  AjaxSolr.extend(this, attributes);
12};
13
14/**
15 * A class 'extends' itself into a subclass.
16 *
17 * @static
18 * @param protoProps The properties of the subclass.
19 * @returns A function that represents the subclass.
20 * @see https://github.com/documentcloud/backbone/blob/51eed189bf4d25877be4acdf51e0a4c6039583c5/backbone.js#L1516
21 */
22AjaxSolr.Class.extend = function (protoProps, staticProps) {
23  var parent = this;
24  var child;
25
26  // The constructor function for the new subclass is either defined by you
27  // (the "constructor" property in your `extend` definition), or defaulted
28  // by us to simply call the parent's constructor.
29  if (protoProps && Object.prototype.hasOwnProperty.call(protoProps, 'constructor')) {
30    child = protoProps.constructor;
31  } else {
32    child = function(){ return parent.apply(this, arguments); };
33  }
34
35  // Add static properties to the constructor function, if supplied.
36  AjaxSolr.extend(child, parent, staticProps);
37
38  // Set the prototype chain to inherit from `parent`, without calling
39  // `parent`'s constructor function.
40  var Surrogate = function(){ this.constructor = child; };
41  Surrogate.prototype = parent.prototype;
42  child.prototype = new Surrogate;
43
44  // Add prototype properties (instance properties) to the subclass,
45  // if supplied.
46  if (protoProps) AjaxSolr.extend(child.prototype, protoProps);
47
48  // Set a convenience property in case the parent's prototype is needed
49  // later.
50  child.__super__ = parent.prototype;
51
52  return child;
53};
54
55/**
56 * @static
57 * @see https://github.com/documentcloud/underscore/blob/7342e289aa9d91c5aacfb3662ea56e7a6d081200/underscore.js#L789
58*/
59AjaxSolr.extend = function (child) {
60  // From _.extend
61  var obj = Array.prototype.slice.call(arguments, 1);
62
63  // From _.extend
64  var iterator = function(source) {
65    if (source) {
66      for (var prop in source) {
67        child[prop] = source[prop];
68      }
69    }
70  };
71
72  // From _.each
73  if (obj == null) return;
74  if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
75    obj.forEach(iterator);
76  } else if (obj.length === +obj.length) {
77    for (var i = 0, l = obj.length; i < l; i++) {
78      iterator.call(undefined, obj[i], i, obj);
79    }
80  } else {
81    for (var key in obj) {
82      if (Object.prototype.hasOwnProperty.call(obj, key)) {
83        iterator.call(undefined, obj[key], key, obj);
84      }
85    }
86  }
87
88  return child;
89};
90
91/**
92 * @static
93 * @param value A value.
94 * @param array An array.
95 * @returns {Boolean} Whether value exists in the array.
96 */
97AjaxSolr.inArray = function (value, array) {
98  if (array) {
99    for (var i = 0, l = array.length; i < l; i++) {
100      if (AjaxSolr.equals(array[i], value)) {
101        return i;
102      }
103    }
104  }
105  return -1;
106};
107
108/**
109 * @static
110 * @param foo A value.
111 * @param bar A value.
112 * @returns {Boolean} Whether the two given values are equal.
113 */
114AjaxSolr.equals = function (foo, bar) {
115  if (AjaxSolr.isArray(foo) && AjaxSolr.isArray(bar)) {
116    if (foo.length !== bar.length) {
117      return false;
118    }
119    for (var i = 0, l = foo.length; i < l; i++) {
120      if (foo[i] !== bar[i]) {
121        return false;
122      }
123    }
124    return true;
125  }
126  else if (AjaxSolr.isRegExp(foo) && AjaxSolr.isString(bar)) {
127    return bar.match(foo);
128  }
129  else if (AjaxSolr.isRegExp(bar) && AjaxSolr.isString(foo)) {
130    return foo.match(bar);
131  }
132  else {
133    return foo === bar;
134  }
135};
136
137/**
138 * Can't use toString.call(obj) === "[object Array]", as it may return
139 * "[xpconnect wrapped native prototype]", which is undesirable.
140 *
141 * @static
142 * @see http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/
143 * @see http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js
144 */
145AjaxSolr.isArray = function (obj) {
146  return obj != null && typeof obj == 'object' && 'splice' in obj && 'join' in obj;
147};
148
149/**
150 * @param obj Any object.
151 * @returns {Boolean} Whether the object is a RegExp object.
152 */
153AjaxSolr.isRegExp = function (obj) {
154  return obj != null && (typeof obj == 'object' || typeof obj == 'function') && 'ignoreCase' in obj;
155};
156
157/**
158 * @param obj Any object.
159 * @returns {Boolean} Whether the object is a String object.
160 */
161AjaxSolr.isString = function (obj) {
162  return obj != null && typeof obj == 'string';
163};
164(function (callback) {
165  if (typeof define === 'function' && define.amd) {
166    define(['core/Core'], callback);
167  }
168  else {
169    callback();
170  }
171}(function () {
172
173/**
174 * The Manager acts as the controller in a Model-View-Controller framework. All
175 * public calls should be performed on the manager object.
176 *
177 * @param properties A map of fields to set. Refer to the list of public fields.
178 * @class AbstractManager
179 */
180AjaxSolr.AbstractManager = AjaxSolr.Class.extend(
181  /** @lends AjaxSolr.AbstractManager.prototype */
182  {
183  /**
184   * @param {Object} [attributes]
185   * @param {String} [attributes.solrUrl] The fully-qualified URL of the Solr
186   *   application. You must include the trailing slash. Do not include the path
187   *   to any Solr servlet. Defaults to "http://localhost:8983/solr/"
188   * @param {String} [attributes.proxyUrl] If we want to proxy queries through a
189   *   script, rather than send queries to Solr directly, set this field to the
190   *   fully-qualified URL of the script.
191   * @param {String} [attributes.servlet] The default Solr servlet. You may
192   *   prepend the servlet with a core if using multiple cores. Defaults to
193   *   "servlet".
194   */
195  constructor: function (attributes) {
196    AjaxSolr.extend(this, {
197      solrUrl: 'http://localhost:8983/solr/',
198      proxyUrl: null,
199      servlet: 'select',
200      // The most recent response from Solr.
201      response: {},
202      // A collection of all registered widgets.
203      widgets: {},
204      // The parameter store for the manager and its widgets.
205      store: null,
206      // Whether <tt>init()</tt> has been called yet.
207      initialized: false
208    }, attributes);
209  },
210
211  /**
212   * An abstract hook for child implementations.
213   *
214   * <p>This method should be called after the store and the widgets have been
215   * added. It should initialize the widgets and the store, and do any other
216   * one-time initializations, e.g., perform the first request to Solr.</p>
217   *
218   * <p>If no store has been set, it sets the store to the basic <tt>
219   * AjaxSolr.ParameterStore</tt>.</p>
220   */
221  init: function () {
222    this.initialized = true;
223    if (this.store === null) {
224      this.setStore(new AjaxSolr.ParameterStore());
225    }
226    this.store.load(false);
227    for (var widgetId in this.widgets) {
228      this.widgets[widgetId].init();
229    }
230    this.store.init();
231  },
232
233  /**
234   * Set the manager's parameter store.
235   *
236   * @param {AjaxSolr.ParameterStore} store
237   */
238  setStore: function (store) {
239    store.manager = this;
240    this.store = store;
241  },
242
243  /**
244   * Adds a widget to the manager.
245   *
246   * @param {AjaxSolr.AbstractWidget} widget
247   */
248  addWidget: function (widget) {
249    widget.manager = this;
250    this.widgets[widget.id] = widget;
251  },
252
253  /**
254   * Stores the Solr parameters to be sent to Solr and sends a request to Solr.
255   *
256   * @param {Boolean} [start] The Solr start offset parameter.
257   * @param {String} [servlet] The Solr servlet to send the request to.
258   */
259  doRequest: function (start, servlet) {
260    if (this.initialized === false) {
261      this.init();
262    }
263    // Allow non-pagination widgets to reset the offset parameter.
264    if (start !== undefined) {
265      this.store.get('start').val(start);
266    }
267    if (servlet === undefined) {
268      servlet = this.servlet;
269    }
270
271    this.store.save();
272
273    for (var widgetId in this.widgets) {
274      this.widgets[widgetId].beforeRequest();
275    }
276
277    this.executeRequest(servlet);
278  },
279
280  /**
281   * An abstract hook for child implementations.
282   *
283   * <p>Sends the request to Solr, i.e. to <code>this.solrUrl</code> or <code>
284   * this.proxyUrl</code>, and receives Solr's response. It should pass Solr's
285   * response to <code>handleResponse()</code> for handling.</p>
286   *
287   * <p>See <tt>managers/Manager.jquery.js</tt> for a jQuery implementation.</p>
288   *
289   * @param {String} servlet The Solr servlet to send the request to.
290   * @param {String} string The query string of the request. If not set, it
291   *   should default to <code>this.store.string()</code>
292   * @throws If not defined in child implementation.
293   */
294  executeRequest: function (servlet, string) {
295    throw 'Abstract method executeRequest must be overridden in a subclass.';
296  },
297
298  /**
299   * This method is executed after the Solr response data arrives. Allows each
300   * widget to handle Solr's response separately.
301   *
302   * @param {Object} data The Solr response.
303   */
304  handleResponse: function (data) {
305    this.response = data;
306
307    for (var widgetId in this.widgets) {
308      this.widgets[widgetId].afterRequest();
309    }
310  },
311
312  /**
313   * This method is executed if Solr encounters an error.
314   *
315   * @param {String} message An error message.
316   */
317  handleError: function (message) {
318    window.console && console.log && console.log(message);
319  }
320});
321
322}));
323(function (callback) {
324  if (typeof define === 'function' && define.amd) {
325    define(['core/Core', 'core/Parameter'], callback);
326  }
327  else {
328    callback();
329  }
330}(function () {
331
332/**
333 * The ParameterStore, as its name suggests, stores Solr parameters. Widgets
334 * expose some of these parameters to the user. Whenever the user changes the
335 * values of these parameters, the state of the application changes. In order to
336 * allow the user to move back and forth between these states with the browser's
337 * Back and Forward buttons, and to bookmark these states, each state needs to
338 * be stored. The easiest method is to store the exposed parameters in the URL
339 * hash (see the <tt>ParameterHashStore</tt> class). However, you may implement
340 * your own storage method by extending this class.
341 *
342 * <p>For a list of possible parameters, please consult the links below.</p>
343 *
344 * @see http://wiki.apache.org/solr/CoreQueryParameters
345 * @see http://wiki.apache.org/solr/CommonQueryParameters
346 * @see http://wiki.apache.org/solr/SimpleFacetParameters
347 * @see http://wiki.apache.org/solr/HighlightingParameters
348 * @see http://wiki.apache.org/solr/MoreLikeThis
349 * @see http://wiki.apache.org/solr/SpellCheckComponent
350 * @see http://wiki.apache.org/solr/StatsComponent
351 * @see http://wiki.apache.org/solr/TermsComponent
352 * @see http://wiki.apache.org/solr/TermVectorComponent
353 * @see http://wiki.apache.org/solr/LocalParams
354 *
355 * @param properties A map of fields to set. Refer to the list of public fields.
356 * @class ParameterStore
357 */
358AjaxSolr.ParameterStore = AjaxSolr.Class.extend(
359  /** @lends AjaxSolr.ParameterStore.prototype */
360  {
361  constructor: function (attributes) {
362    /**
363     * @param {Object} [attributes]
364     * @param {String[]} [attributes.exposed] The names of the exposed
365     *   parameters. Any parameters that your widgets expose to the user,
366     *   directly or indirectly, should be listed here.
367     */
368    AjaxSolr.extend(this, {
369      exposed: [],
370      // The Solr parameters.
371      params: {},
372      // A reference to the parameter store's manager.
373      manager: null
374    }, attributes);
375  },
376
377  /**
378   * An abstract hook for child implementations.
379   *
380   * <p>This method should do any necessary one-time initializations.</p>
381   */
382  init: function () {},
383
384  /**
385   * Some Solr parameters may be specified multiple times. It is easiest to
386   * hard-code a list of such parameters. You may change the list by passing
387   * <code>{ multiple: /pattern/ }</code> as an argument to the constructor of
388   * this class or one of its children, e.g.:
389   *
390   * <p><code>new ParameterStore({ multiple: /pattern/ })</code>
391   *
392   * @param {String} name The name of the parameter.
393   * @returns {Boolean} Whether the parameter may be specified multiple times.
394   * @see http://lucene.apache.org/solr/api/org/apache/solr/handler/DisMaxRequestHandler.html
395   */
396  isMultiple: function (name) {
397    return name.match(/^(?:bf|bq|facet\.date|facet\.date\.other|facet\.date\.include|facet\.field|facet\.pivot|facet\.range|facet\.range\.other|facet\.range\.include|facet\.query|fq|group\.field|group\.func|group\.query|pf|qf)$/);
398  },
399
400  /**
401   * Returns a parameter. If the parameter doesn't exist, creates it.
402   *
403   * @param {String} name The name of the parameter.
404   * @returns {AjaxSolr.Parameter|AjaxSolr.Parameter[]} The parameter.
405   */
406  get: function (name) {
407    if (this.params[name] === undefined) {
408      var param = new AjaxSolr.Parameter({ name: name });
409      if (this.isMultiple(name)) {
410        this.params[name] = [ param ];
411      }
412      else {
413        this.params[name] = param;
414      }
415    }
416    return this.params[name];
417  },
418
419  /**
420   * If the parameter may be specified multiple times, returns the values of
421   * all identically-named parameters. If the parameter may be specified only
422   * once, returns the value of that parameter.
423   *
424   * @param {String} name The name of the parameter.
425   * @returns {String[]|Number[]} The value(s) of the parameter.
426   */
427  values: function (name) {
428    if (this.params[name] !== undefined) {
429      if (this.isMultiple(name)) {
430        var values = [];
431        for (var i = 0, l = this.params[name].length; i < l; i++) {
432          values.push(this.params[name][i].val());
433        }
434        return values;
435      }
436      else {
437        return [ this.params[name].val() ];
438      }
439    }
440    return [];
441  },
442
443  /**
444   * If the parameter may be specified multiple times, adds the given parameter
445   * to the list of identically-named parameters, unless one already exists with
446   * the same value. If it may be specified only once, replaces the parameter.
447   *
448   * @param {String} name The name of the parameter.
449   * @param {AjaxSolr.Parameter} [param] The parameter.
450   * @returns {AjaxSolr.Parameter|Boolean} The parameter, or false.
451   */
452  add: function (name, param) {
453    if (param === undefined) {
454      param = new AjaxSolr.Parameter({ name: name });
455    }
456    if (this.isMultiple(name)) {
457      if (this.params[name] === undefined) {
458        this.params[name] = [ param ];
459      }
460      else {
461        if (AjaxSolr.inArray(param.val(), this.values(name)) == -1) {
462          this.params[name].push(param);
463        }
464        else {
465          return false;
466        }
467      }
468    }
469    else {
470      this.params[name] = param;
471    }
472    return param;
473  },
474
475  /**
476   * Deletes a parameter.
477   *
478   * @param {String} name The name of the parameter.
479   * @param {Number} [index] The index of the parameter.
480   */
481  remove: function (name, index) {
482    if (index === undefined) {
483      delete this.params[name];
484    }
485    else {
486      this.params[name].splice(index, 1);
487      if (this.params[name].length == 0) {
488        delete this.params[name];
489      }
490    }
491  },
492
493  /**
494   * Finds all parameters with matching values.
495   *
496   * @param {String} name The name of the parameter.
497   * @param {String|Number|String[]|Number[]|RegExp} value The value.
498   * @returns {String|Number[]} The indices of the parameters found.
499   */
500  find: function (name, value) {
501    if (this.params[name] !== undefined) {
502      if (this.isMultiple(name)) {
503        var indices = [];
504        for (var i = 0, l = this.params[name].length; i < l; i++) {
505          if (AjaxSolr.equals(this.params[name][i].val(), value)) {
506            indices.push(i);
507          }
508        }
509        return indices.length ? indices : false;
510      }
511      else {
512        if (AjaxSolr.equals(this.params[name].val(), value)) {
513          return name;
514        }
515      }
516    }
517    return false;
518  },
519
520  /**
521   * If the parameter may be specified multiple times, creates a parameter using
522   * the given name and value, and adds it to the list of identically-named
523   * parameters, unless one already exists with the same value. If it may be
524   * specified only once, replaces the parameter.
525   *
526   * @param {String} name The name of the parameter.
527   * @param {String|Number|String[]|Number[]} value The value.
528   * @param {Object} [locals] The parameter's local parameters.
529   * @returns {AjaxSolr.Parameter|Boolean} The parameter, or false.
530   */
531  addByValue: function (name, value, locals) {
532    if (locals === undefined) {
533      locals = {};
534    }
535    if (this.isMultiple(name) && AjaxSolr.isArray(value)) {
536      var ret = [];
537      for (var i = 0, l = value.length; i < l; i++) {
538        ret.push(this.add(name, new AjaxSolr.Parameter({ name: name, value: value[i], locals: locals })));
539      }
540      return ret;
541    }
542    else {
543      return this.add(name, new AjaxSolr.Parameter({ name: name, value: value, locals: locals }));
544    }
545  },
546
547  /**
548   * Deletes any parameter with a matching value.
549   *
550   * @param {String} name The name of the parameter.
551   * @param {String|Number|String[]|Number[]|RegExp} value The value.
552   * @returns {String|Number[]} The indices deleted.
553   */
554  removeByValue: function (name, value) {
555    var indices = this.find(name, value);
556    if (indices) {
557      if (AjaxSolr.isArray(indices)) {
558        for (var i = indices.length - 1; i >= 0; i--) {
559          this.remove(name, indices[i]);
560        }
561      }
562      else {
563        this.remove(indices);
564      }
565    }
566    return indices;
567  },
568
569  /**
570   * Returns the Solr parameters as a query string.
571   *
572   * <p>IE6 calls the default toString() if you write <tt>store.toString()
573   * </tt>. So, we need to choose another name for toString().</p>
574   */
575  string: function () {
576    var params = [], string;
577    for (var name in this.params) {
578      if (this.isMultiple(name)) {
579        for (var i = 0, l = this.params[name].length; i < l; i++) {
580          string = this.params[name][i].string();
581          if (string) {
582            params.push(string);
583          }
584        }
585      }
586      else {
587        string = this.params[name].string();
588        if (string) {
589          params.push(string);
590        }
591      }
592    }
593    return params.join('&');
594  },
595
596  /**
597   * Parses a query string into Solr parameters.
598   *
599   * @param {String} str The string to parse.
600   */
601  parseString: function (str) {
602    var pairs = str.split('&');
603    for (var i = 0, l = pairs.length; i < l; i++) {
604      if (pairs[i]) { // ignore leading, trailing, and consecutive &'s
605        var param = new AjaxSolr.Parameter();
606        param.parseString(pairs[i]);
607        this.add(param.name, param);
608      }
609    }
610  },
611
612  /**
613   * Returns the exposed parameters as a query string.
614   *
615   * @returns {String} A string representation of the exposed parameters.
616   */
617  exposedString: function () {
618    var params = [], string;
619    for (var i = 0, l = this.exposed.length; i < l; i++) {
620      if (this.params[this.exposed[i]] !== undefined) {
621        if (this.isMultiple(this.exposed[i])) {
622          for (var j = 0, m = this.params[this.exposed[i]].length; j < m; j++) {
623            string = this.params[this.exposed[i]][j].string();
624            if (string) {
625              params.push(string);
626            }
627          }
628        }
629        else {
630          string = this.params[this.exposed[i]].string();
631          if (string) {
632            params.push(string);
633          }
634        }
635      }
636    }
637    return params.join('&');
638  },
639
640  /**
641   * Resets the values of the exposed parameters.
642   */
643  exposedReset: function () {
644    for (var i = 0, l = this.exposed.length; i < l; i++) {
645      this.remove(this.exposed[i]);
646    }
647  },
648
649  /**
650   * Loads the values of exposed parameters from persistent storage. It is
651   * necessary, in most cases, to reset the values of exposed parameters before
652   * setting the parameters to the values in storage. This is to ensure that a
653   * parameter whose name is not present in storage is properly reset.
654   *
655   * @param {Boolean} [reset=true] Whether to reset the exposed parameters.
656   *   before loading new values from persistent storage. Default: true.
657   */
658  load: function (reset) {
659    if (reset === undefined) {
660      reset = true;
661    }
662    if (reset) {
663      this.exposedReset();
664    }
665    this.parseString(this.storedString());
666  },
667
668  /**
669   * An abstract hook for child implementations.
670   *
671   * <p>Stores the values of the exposed parameters in persistent storage. This
672   * method should usually be called before each Solr request.</p>
673   */
674  save: function () {},
675
676  /**
677   * An abstract hook for child implementations.
678   *
679   * <p>Returns the string to parse from persistent storage.</p>
680   *
681   * @returns {String} The string from persistent storage.
682   */
683  storedString: function () {
684    return '';
685  }
686});
687
688}));
689(function (callback) {
690  if (typeof define === 'function' && define.amd) {
691    define(['core/Core'], callback);
692  }
693  else {
694    callback();
695  }
696}(function () {
697
698/**
699 * Represents a Solr parameter.
700 *
701 * @param properties A map of fields to set. Refer to the list of public fields.
702 * @class Parameter
703 */
704AjaxSolr.Parameter = AjaxSolr.Class.extend(
705  /** @lends AjaxSolr.Parameter.prototype */
706  {
707  /**
708   * @param {Object} attributes
709   * @param {String} attributes.name The parameter's name.
710   * @param {String} [attributes.value] The parameter's value.
711   * @param {Object} [attributes.local] The parameter's local parameters.
712   */
713  constructor: function (attributes) {
714    AjaxSolr.extend(this, {
715      name: null,
716      value: null,
717      locals: {}
718    }, attributes);
719  },
720
721  /**
722   * Returns the value. If called with an argument, sets the value.
723   *
724   * @param {String|Number|String[]|Number[]} [value] The value to set.
725   * @returns The value.
726   */
727  val: function (value) {
728    if (value === undefined) {
729      return this.value;
730    }
731    else {
732      this.value = value;
733    }
734  },
735
736  /**
737   * Returns the value of a local parameter. If called with a second argument,
738   * sets the value of a local parameter.
739   *
740   * @param {String} name The name of the local parameter.
741   * @param {String|Number|String[]|Number[]} [value] The value to set.
742   * @returns The value.
743   */
744  local: function (name, value) {
745    if (value === undefined) {
746      return this.locals[name];
747    }
748    else {
749      this.locals[name] = value;
750    }
751  },
752
753  /**
754   * Deletes a local parameter.
755   *
756   * @param {String} name The name of the local parameter.
757   */
758  remove: function (name) {
759    delete this.locals[name];
760  },
761
762  /**
763   * Returns the Solr parameter as a query string key-value pair.
764   *
765   * <p>IE6 calls the default toString() if you write <tt>store.toString()
766   * </tt>. So, we need to choose another name for toString().</p>
767   */
768  string: function () {
769    var pairs = [];
770
771    for (var name in this.locals) {
772      if (this.locals[name]) {
773        pairs.push(name + '=' + encodeURIComponent(this.locals[name]));
774      }
775    }
776
777    var prefix = pairs.length ? '{!' + pairs.join('%20') + '}' : '';
778
779    if (this.value) {
780      return this.name + '=' + prefix + this.valueString(this.value);
781    }
782    // For dismax request handlers, if the q parameter has local params, the
783    // q parameter must be set to a non-empty value. In case the q parameter
784    // has local params but is empty, use the q.alt parameter, which accepts
785    // wildcards.
786    else if (this.name == 'q' && prefix) {
787      return 'q.alt=' + prefix + encodeURIComponent('*:*');
788    }
789    else {
790      return '';
791    }
792  },
793
794  /**
795   * Parses a string formed by calling string().
796   *
797   * @param {String} str The string to parse.
798   */
799  parseString: function (str) {
800    var param = str.match(/^([^=]+)=(?:\{!([^\}]*)\})?(.*)$/);
801    if (param) {
802      var matches;
803
804      while (matches = /([^\s=]+)=(\S*)/g.exec(decodeURIComponent(param[2]))) {
805        this.locals[matches[1]] = decodeURIComponent(matches[2]);
806        param[2] = param[2].replace(matches[0], ''); // Safari's exec seems not to do this on its own
807      }
808
809      if (param[1] == 'q.alt') {
810        this.name = 'q';
811        // if q.alt is present, assume it is because q was empty, as above
812      }
813      else {
814        this.name = param[1];
815        this.value = this.parseValueString(param[3]);
816      }
817    }
818  },
819
820  /**
821   * Returns the value as a URL-encoded string.
822   *
823   * @private
824   * @param {String|Number|String[]|Number[]} value The value.
825   * @returns {String} The URL-encoded string.
826   */
827  valueString: function (value) {
828    value = AjaxSolr.isArray(value) ? value.join(',') : value;
829    return encodeURIComponent(value);
830  },
831
832  /**
833   * Parses a URL-encoded string to return the value.
834   *
835   * @private
836   * @param {String} str The URL-encoded string.
837   * @returns {Array} The value.
838   */
839  parseValueString: function (str) {
840    str = decodeURIComponent(str);
841    return str.indexOf(',') == -1 ? str : str.split(',');
842  }
843});
844
845/**
846 * Escapes a value, to be used in, for example, an fq parameter. Surrounds
847 * strings containing spaces or colons in double quotes.
848 *
849 * @public
850 * @param {String|Number} value The value.
851 * @returns {String} The escaped value.
852 */
853AjaxSolr.Parameter.escapeValue = function (value) {
854  // If the field value has a space, colon, quotation mark or forward slash
855  // in it, wrap it in quotes, unless it is a range query or it is already
856  // wrapped in quotes.
857  if (value.match(/[ :\/"]/) && !value.match(/[\[\{]\S+ TO \S+[\]\}]/) && !value.match(/^["\(].*["\)]$/)) {
858    return '"' + value.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
859  }
860  return value;
861}
862
863}));
864(function (callback) {
865  if (typeof define === 'function' && define.amd) {
866    define(['core/Core'], callback);
867  }
868  else {
869    callback();
870  }
871}(function () {
872
873/**
874 * Baseclass for all widgets.
875 *
876 * Provides abstract hooks for child classes.
877 *
878 * @param properties A map of fields to set. May be new or public fields.
879 * @class AbstractWidget
880 */
881AjaxSolr.AbstractWidget = AjaxSolr.Class.extend(
882  /** @lends AjaxSolr.AbstractWidget.prototype */
883  {
884  /**
885   * @param {Object} attributes
886   * @param {String} attributes.id A unique identifier of this widget.
887   * @param {String} [attributes.target] The CSS selector for this widget's
888   *   target HTML element, e.g. a specific <tt>div</tt> or <tt>ul</tt>. A
889   *   Widget is usually implemented to perform all its UI changes relative to
890   *   its target HTML element.
891   * @param {Number} [attributes.start] The offset parameter. Set this field to
892   *   make the widget reset the offset parameter to the given value on each
893   *   request.
894   * @param {String} [attributes.servlet] The Solr servlet for this widget. You
895   *   may prepend the servlet with a core if using multiple cores. If none is
896   *   set, it will default to the manager's servlet.
897   */
898  constructor: function (attributes) {
899    AjaxSolr.extend(this, {
900      id: null,
901      target: null,
902      start: undefined,
903      servlet: undefined,
904      // A reference to the widget's manager.
905      manager: null
906    }, attributes);
907  },
908
909  /**
910   * An abstract hook for child implementations.
911   *
912   * <p>This method should do any necessary one-time initializations.</p>
913   */
914  init: function () {},
915
916  /**
917   * An abstract hook for child implementations.
918   *
919   * <p>This method is executed before the Solr request is sent.</p>
920   */
921  beforeRequest: function () {},
922
923  /**
924   * An abstract hook for child implementations.
925   *
926   * <p>This method is executed after the Solr response is received.</p>
927   */
928  afterRequest: function () {},
929
930  /**
931   * A proxy to the manager's doRequest method.
932   *
933   * @param {Boolean} [start] The Solr start offset parameter.
934   * @param {String} [servlet] The Solr servlet to send the request to.
935   */
936  doRequest: function (start, servlet) {
937    this.manager.doRequest(start || this.start, servlet || this.servlet);
938  }
939});
940
941}));
942(function (callback) {
943  if (typeof define === 'function' && define.amd) {
944    define(['core/AbstractWidget', 'core/Parameter'], callback);
945  }
946  else {
947    callback();
948  }
949}(function () {
950
951/**
952 * Baseclass for all facet widgets.
953 *
954 * @class AbstractFacetWidget
955 * @augments AjaxSolr.AbstractWidget
956 */
957AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend(
958  /** @lends AjaxSolr.AbstractFacetWidget.prototype */
959  {
960  /**
961   * @param {Object} attributes
962   * @param {String} attributes.field The field to facet on.
963   * @param {Number} [attributes.start] This widget will by default set the
964   *   offset parameter to 0 on each request.
965   * @param {Boolean} [attributes.multivalue] Set to <tt>false</tt> to force a
966   *   single "fq" parameter for this widget. Defaults to <tt>true</tt>.
967   */
968  constructor: function (attributes) {
969    AjaxSolr.AbstractFacetWidget.__super__.constructor.apply(this, arguments);
970    AjaxSolr.extend(this, {
971      start: 0,
972      field: null,
973      multivalue: true
974    }, attributes);
975  },
976
977  init: function () {
978    this.initStore();
979  },
980
981  /**
982   * Add facet parameters to the parameter store.
983   */
984  initStore: function () {
985    /* http://wiki.apache.org/solr/SimpleFacetParameters */
986    var parameters = [
987      'facet.prefix',
988      'facet.sort',
989      'facet.limit',
990      'facet.offset',
991      'facet.mincount',
992      'facet.missing',
993      'facet.method',
994      'facet.enum.cache.minDf'
995    ];
996
997    this.manager.store.addByValue('facet', true);
998
999    // Set facet.field, facet.date or facet.range to truthy values to add
1000    // related per-field parameters to the parameter store.
1001    if (this['facet.field'] !== undefined) {
1002      this.manager.store.addByValue('facet.field', this.field);
1003    }
1004    else if (this['facet.date'] !== undefined) {
1005      this.manager.store.addByValue('facet.date', this.field);
1006      parameters = parameters.concat([
1007        'facet.date.start',
1008        'facet.date.end',
1009        'facet.date.gap',
1010        'facet.date.hardend',
1011        'facet.date.other',
1012        'facet.date.include'
1013      ]);
1014    }
1015    else if (this['facet.range'] !== undefined) {
1016      this.manager.store.addByValue('facet.range', this.field);
1017      parameters = parameters.concat([
1018        'facet.range.start',
1019        'facet.range.end',
1020        'facet.range.gap',
1021        'facet.range.hardend',
1022        'facet.range.other',
1023        'facet.range.include'
1024      ]);
1025    }
1026
1027    for (var i = 0, l = parameters.length; i < l; i++) {
1028      if (this[parameters[i]] !== undefined) {
1029        this.manager.store.addByValue('f.' + this.field + '.' + parameters[i], this[parameters[i]]);
1030      }
1031    }
1032  },
1033
1034  /**
1035   * @returns {Boolean} Whether any filter queries have been set using this
1036   *   widget's facet field.
1037   */
1038  isEmpty: function () {
1039    return !this.manager.store.find('fq', new RegExp('^-?' + this.field + ':'));
1040  },
1041
1042  /**
1043   * Sets the filter query.
1044   *
1045   * @returns {Boolean} Whether the selection changed.
1046   */
1047  set: function (value) {
1048    return this.changeSelection(function () {
1049      var a = this.manager.store.removeByValue('fq', new RegExp('^-?' + this.field + ':')),
1050          b = this.manager.store.addByValue('fq', this.fq(value));
1051      return a || b;
1052    });
1053  },
1054
1055  /**
1056   * Adds a filter query.
1057   *
1058   * @returns {Boolean} Whether a filter query was added.
1059   */
1060  add: function (value) {
1061    return this.changeSelection(function () {
1062      return this.manager.store.addByValue('fq', this.fq(value));
1063    });
1064  },
1065
1066  /**
1067   * Removes a filter query.
1068   *
1069   * @returns {Boolean} Whether a filter query was removed.
1070   */
1071  remove: function (value) {
1072    return this.changeSelection(function () {
1073      return this.manager.store.removeByValue('fq', this.fq(value));
1074    });
1075  },
1076
1077  /**
1078   * Removes all filter queries using the widget's facet field.
1079   *
1080   * @returns {Boolean} Whether a filter query was removed.
1081   */
1082  clear: function () {
1083    return this.changeSelection(function () {
1084      return this.manager.store.removeByValue('fq', new RegExp('^-?' + this.field + ':'));
1085    });
1086  },
1087
1088  /**
1089   * Helper for selection functions.
1090   *
1091   * @param {Function} Selection function to call.
1092   * @returns {Boolean} Whether the selection changed.
1093   */
1094  changeSelection: function (func) {
1095    changed = func.apply(this);
1096    if (changed) {
1097      this.afterChangeSelection();
1098    }
1099    return changed;
1100  },
1101
1102  /**
1103   * An abstract hook for child implementations.
1104   *
1105   * <p>This method is executed after the filter queries change.</p>
1106   */
1107  afterChangeSelection: function () {},
1108
1109  /**
1110   * One of "facet.field", "facet.date" or "facet.range" must be set on the
1111   * widget in order to determine where the facet counts are stored.
1112   *
1113   * @returns {Array} An array of objects with the properties <tt>facet</tt> and
1114   * <tt>count</tt>, e.g <tt>
1114{ facet: 'facet', count: 1 }</tt>.
1115   */
1116  getFacetCounts: function () {
1117    var property;
1118    if (this['facet.field'] !== undefined) {
1119      property = 'facet_fields';
1120    }
1121    else if (this['facet.date'] !== undefined) {
1122      property = 'facet_dates';
1123    }
1124    else if (this['facet.range'] !== undefined) {
1125      property = 'facet_ranges';
1126    }
1127    if (property !== undefined) {
1128      switch (this.manager.store.get('json.nl').val()) {
1129        case 'map':
1130          return this.getFacetCountsMap(property);
1131        case 'arrarr':
1132          return this.getFacetCountsArrarr(property);
1133        default:
1134          return this.getFacetCountsFlat(property);
1135      }
1136    }
1137    throw 'Cannot get facet counts unless one of the following properties is set to "true" on widget "' + this.id + '": "facet.field", "facet.date", or "facet.range".';
1138  },
1139
1140  /**
1141   * Used if the facet counts are represented as a JSON object.
1142   *
1143   * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
1144   * @returns {Array} An array of objects with the properties <tt>facet</tt> and
1145   * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
1146   */
1147  getFacetCountsMap: function (property) {
1148    var counts = [];
1149    for (var facet in this.manager.response.facet_counts[property][this.field]) {
1150      counts.push({
1151        facet: facet,
1152        count: parseInt(this.manager.response.facet_counts[property][this.field][facet])
1153      });
1154    }
1155    return counts;
1156  },
1157
1158  /**
1159   * Used if the facet counts are represented as an array of two-element arrays.
1160   *
1161   * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
1162   * @returns {Array} An array of objects with the properties <tt>facet</tt> and
1163   * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
1164   */
1165  getFacetCountsArrarr: function (property) {
1166    var counts = [];
1167    for (var i = 0, l = this.manager.response.facet_counts[property][this.field].length; i < l; i++) {
1168      counts.push({
1169        facet: this.manager.response.facet_counts[property][this.field][i][0],
1170        count: parseInt(this.manager.response.facet_counts[property][this.field][i][1])
1171      });
1172    }
1173    return counts;
1174  },
1175
1176  /**
1177   * Used if the facet counts are represented as a flat array.
1178   *
1179   * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
1180   * @returns {Array} An array of objects with the properties <tt>facet</tt> and
1181   * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
1182   */
1183  getFacetCountsFlat: function (property) {
1184    var counts = [];
1185    for (var i = 0, l = this.manager.response.facet_counts[property][this.field].length; i < l; i += 2) {
1186      counts.push({
1187        facet: this.manager.response.facet_counts[property][this.field][i],
1188        count: parseInt(this.manager.response.facet_counts[property][this.field][i+1])
1189      });
1190    }
1191    return counts;
1192  },
1193
1194  /**
1195   * @param {String} value The value.
1196   * @returns {Function} Sends a request to Solr if it successfully adds a
1197   *   filter query with the given value.
1198   */
1199  clickHandler: function (value) {
1200    var self = this, meth = this.multivalue ? 'add' : 'set';
1201    return function () {
1202      if (self[meth].call(self, value)) {
1203        self.doRequest();
1204      }
1205      return false;
1206    }
1207  },
1208
1209  /**
1210   * @param {String} value The value.
1211   * @returns {Function} Sends a request to Solr if it successfully removes a
1212   *   filter query with the given value.
1213   */
1214  unclickHandler: function (value) {
1215    var self = this;
1216    return function () {
1217      if (self.remove(value)) {
1218        self.doRequest();
1219      }
1220      return false;
1221    }
1222  },
1223
1224  /**
1225   * @param {String} value The facet value.
1226   * @param {Boolean} exclude Whether to exclude this fq parameter value.
1227   * @returns {String} An fq parameter value.
1228   */
1229  fq: function (value, exclude) {
1230    return (exclude ? '-' : '') + this.field + ':' + AjaxSolr.Parameter.escapeValue(value);
1231  }
1232});
1233
1234}));
1235(function (callback) {
1236  if (typeof define === 'function' && define.amd) {
1237    define(['core/AbstractWidget'], callback);
1238  }
1239  else {
1240    callback();
1241  }
1242}(function () {
1243
1244/**
1245 * Offers an interface to the local parameters used by the Spatial Solr plugin.
1246 *
1247 * @see http://www.jteam.nl/news/spatialsolr
1248 *
1249 * @class AbstractSpatialWidget
1250 * @augments AjaxSolr.AbstractWidget
1251 */
1252AjaxSolr.AbstractSpatialWidget = AjaxSolr.AbstractWidget.extend(
1253  /** @lends AjaxSolr.AbstractSpatialWidget.prototype */
1254  {
1255  /**
1256   * Sets the Spatial Solr local parameters.
1257   *
1258   * @param {Object}
1258 params The local parameters to set.
1259   * @param {Number} params.lat Latitude of the center of the search area.
1260   * @param {Number} params.lng Longitude of the center of the search area.
1261   * @param {Number} params.radius Radius of the search area.
1262   * @param {String} [params.unit] Unit the distances should be calculated in:
1263   *   "km" or "miles".
1264   * @param {String} [params.calc] <tt>GeoDistanceCalculator</tt> that will be
1265   *   used to calculate the distances. "arc" for
1266   *   <tt>ArchGeoDistanceCalculator</tt> and "plane" for
1267   *   <tt>PlaneGeoDistanceCalculator</tt>.
1268   * @param {Number} [params.threadCount] Number of threads that will be used
1269   *   by the <tt>ThreadedDistanceFilter</tt>.
1270   */
1271  set: function (params) {
1272    this.manager.store.get('q').local('type', 'spatial');
1273    this.manager.store.get('q').local('lat', params.lat);
1274    this.manager.store.get('q').local('long', params.lng);
1275    this.manager.store.get('q').local('radius', params.radius);
1276    if (params.unit !== undefined) {
1277      this.manager.store.get('q').local('unit', params.unit);
1278    }
1279    if (params.calc !== undefined) {
1280      this.manager.store.get('q').local('calc', params.calc);
1281    }
1282    if (params.threadCount !== undefined) {
1283      this.manager.store.get('q').local('threadCount', params.threadCount);
1284    }
1285  },
1286
1287  /**
1288   * Removes the Spatial Solr local parameters.
1289   */
1290  clear: function () {
1291    this.manager.store.get('q').remove('type');
1292    this.manager.store.get('q').remove('lat');
1293    this.manager.store.get('q').remove('long');
1294    this.manager.store.get('q').remove('radius');
1295    this.manager.store.get('q').remove('unit');
1296    this.manager.store.get('q').remove('calc');
1297    this.manager.store.get('q').remove('threadCount');
1298  }
1299});
1300
1301}));
1302(function (callback) {
1303  if (typeof define === 'function' && define.amd) {
1304    define(['core/AbstractWidget'], callback);
1305  }
1306  else {
1307    callback();
1308  }
1309}(function () {
1310
1311/**
1312 * Interacts with Solr's SpellCheckComponent.
1313 *
1314 * @see http://wiki.apache.org/solr/SpellCheckComponent
1315 *
1316 * @class AbstractSpellcheckWidget
1317 * @augments AjaxSolr.AbstractWidget
1318 */
1319AjaxSolr.AbstractSpellcheckWidget = AjaxSolr.AbstractWidget.extend(
1320  /** @lends AjaxSolr.AbstractSpellcheckWidget.prototype */
1321  {
1322  constructor: function (attributes) {
1323    AjaxSolr.AbstractSpellcheckWidget.__super__.constructor.apply(this, arguments);
1324    AjaxSolr.extend(this, {
1325      // The suggestions.
1326      suggestions: {}
1327    }, attributes);
1328  },
1329
1330  /**
1331   * Uses the top suggestion for each word to return a suggested query.
1332   *
1333   * @returns {String} A suggested query.
1334   */
1335  suggestion: function () {
1336    var suggestion = this.manager.response.responseHeader.params['spellcheck.q'];
1337    for (var word in this.suggestions) {
1338      suggestion = suggestion.replace(new RegExp(word, 'g'), this.suggestions[word][0]);
1339    }
1340    return suggestion;
1341  },
1342
1343  beforeRequest: function () {
1344    if (this.manager.store.get('spellcheck').val() && this.manager.store.get('q').val()) {
1345      this.manager.store.get('spellcheck.q').val(this.manager.store.get('q').val());
1346    }
1347    else {
1348      this.manager.store.remove('spellcheck.q');
1349    }
1350  },
1351
1352  afterRequest: function () {
1353    this.suggestions = {};
1354
1355    if (this.manager.response.spellcheck && this.manager.response.spellcheck.suggestions) {
1356      var suggestions = this.manager.response.spellcheck.suggestions,
1357          empty = true;
1358
1359      for (var word in suggestions) {
1360        if (word == 'collation' || word == 'correctlySpelled') continue;
1361
1362        this.suggestions[word] = [];
1363        for (var i = 0, l = suggestions[word].suggestion.length; i < l; i++) {
1364          if (this.manager.response.responseHeader.params['spellcheck.extendedResults']) {
1365            this.suggestions[word].push(suggestions[word].suggestion[i].word);
1366          }
1367          else {
1368            this.suggestions[word].push(suggestions[word].suggestion[i]);
1369          }
1370        }
1371        empty = false;
1372      }
1373
1374      if (!empty) {
1375        this.handleSuggestions(this.manager.response);
1376      }
1377    }
1378  },
1379
1380  /**
1381   * An abstract hook for child implementations.
1382   *
1383   * <p>Allow the child to handle the suggestions without parsing the response.</p>
1384   */
1385  handleSuggestions: function () {}
1386});
1387
1388}));
1389(function (callback) {
1390  if (typeof define === 'function' && define.amd) {
1391    define(['core/AbstractWidget'], callback);
1392  }
1393  else {
1394    callback();
1395  }
1396}(function () {
1397
1398/**
1399 * Baseclass for all free-text widgets.
1400 *
1401 * @class AbstractTextWidget
1402 * @augments AjaxSolr.AbstractWidget
1403 */
1404AjaxSolr.AbstractTextWidget = AjaxSolr.AbstractWidget.extend(
1405  /** @lends AjaxSolr.AbstractTextWidget.prototype */
1406  {
1407  /**
1408   * @param {Object} [attributes]
1409   * @param {Number} [attributes.start] This widget will by default set the
1410   *   offset parameter to 0 on each request.
1411   */
1412  constructor: function (attributes) {
1413    AjaxSolr.AbstractTextWidget.__super__.constructor.apply(this, arguments);
1414    AjaxSolr.extend(this, {
1415      start: 0
1416    }, attributes);
1417  },
1418
1419  /**
1420   * Sets the main Solr query to the given string.
1421   *
1422   * @param {String} q The new Solr query.
1423   * @returns {Boolean} Whether the selection changed.
1424   */
1425  set: function (q) {
1426    return this.changeSelection(function () {
1427      this.manager.store.get('q').val(q);
1428    });
1429  },
1430
1431  /**
1432   * Sets the main Solr query to the empty string.
1433   *
1434   * @returns {Boolean} Whether the selection changed.
1435   */
1436  clear: function () {
1437    return this.changeSelection(function () {
1438      this.manager.store.remove('q');
1439    });
1440  },
1441
1442  /**
1443   * Helper for selection functions.
1444   *
1445   * @param {Function} Selection function to call.
1446   * @returns {Boolean} Whether the selection changed.
1447   */
1448  changeSelection: function (func) {
1449    var before = this.manager.store.get('q').val();
1450    func.apply(this);
1451    var after = this.manager.store.get('q').val();
1452    if (after !== before) {
1453      this.afterChangeSelection(after);
1454    }
1455    return after !== before;
1456  },
1457
1458  /**
1459   * An abstract hook for child implementations.
1460   *
1461   * <p>This method is executed after the main Solr query changes.</p>
1462   *
1463   * @param {String} value The current main Solr query.
1464   */
1465  afterChangeSelection: function (value) {},
1466
1467  /**
1468   * Returns a function to unset the main Solr query.
1469   *
1470   * @returns {Function}
1471   */
1472  unclickHandler: function () {
1473    var self = this;
1474    return function () {
1475      if (self.clear()) {
1476        self.doRequest();
1477      }
1478      return false;
1479    }
1480  },
1481
1482  /**
1483   * Returns a function to set the main Solr query.
1484   *
1485   * @param {String} value The new Solr query.
1486   * @returns {Function}
1487   */
1488  clickHandler: function (q) {
1489    var self = this;
1490    return function () {
1491      if (self.set(q)) {
1492        self.doRequest();
1493      }
1494      return false;
1495    }
1496  }
1497});
1498
1499}));
1500(function (callback) {
1501  if (typeof define === 'function' && define.amd) {
1502    define(['core/ParameterStore'], callback);
1503  }
1504  else {
1505    callback();
1506  }
1507}(function () {
1508
1509/**
1510 * A parameter store that stores the values of exposed parameters in the URL
1511 * hash to maintain the application's state.
1512 *
1513 * <p>The ParameterHashStore observes the hash for changes and loads Solr
1514 * parameters from the hash if it observes a change or if the hash is empty.
1515 * The onhashchange event is used if the browser supports it.</p>
1516 *
1517 * <p>Configure the manager with:</p>
1518 *
1519 * @class ParameterHashStore
1520 * @augments AjaxSolr.ParameterStore
1521 * @see https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange
1522 */
1523AjaxSolr.ParameterHashStore = AjaxSolr.ParameterStore.extend(
1524  /** @lends AjaxSolr.ParameterHashStore.prototype */
1525  {
1526  /**
1527   * @param {Object} [attributes]
1528   * @param {Number} [attributes.interval] The interval in milliseconds to use
1529   *   in <tt>setInterval()</tt>. Do not set the interval too low as you may set
1530   *   up a race condition. Defaults to 250.
1531   */
1532  constructor: function (attributes) {
1533    AjaxSolr.ParameterHashStore.__super__.constructor.apply(this, arguments);
1534    AjaxSolr.extend(this, {
1535      interval: 250,
1536      // Reference to the setInterval() function.
1537      intervalId: null,
1538      // A local copy of the URL hash, so we can detect changes to it.
1539      hash: ''
1540    }, attributes);
1541  },
1542
1543  /**
1544   * If loading and saving the hash take longer than <tt>interval</tt>, we'll
1545   * hit a race condition. However, this should never happen.
1546   */
1547  init: function () {
1548    if (this.exposed.length) {
1549      // Check if the browser supports the onhashchange event. IE 8 and 9 in compatibility mode
1550      // incorrectly report support for onhashchange.
1551      if ('onhashchange' in window && (!document.documentMode || document.documentMode > 7)) {
1552        if (window.addEventListener) {
1553          window.addEventListener('hashchange', this.intervalFunction(this), false);
1554        }
1555        else if (window.attachEvent) {
1556          window.attachEvent('onhashchange', this.intervalFunction(this));
1557        }
1558        else {
1559          window.onhashchange = this.intervalFunction(this);
1560        }
1561      }
1562      else {
1563        this.intervalId = window.setInterval(this.intervalFunction(this), this.interval);
1564      }
1565    }
1566  },
1567
1568  /**
1569   * Stores the values of the exposed parameters in both the local hash and the
1570   * URL hash. No other code should be made to change these two values.
1571   */
1572  save: function () {
1573    this.hash = this.exposedString();
1574    if (this.storedString()) {
1575      // Make a new history entry.
1576      window.location.hash = this.hash;
1577    }
1578    else {
1579      // Replace the old history entry.
1580      window.location.replace(window.location.href.replace('#', '') + '#' + this.hash);
1581    }
1582  },
1583
1584  /**
1585   * @see ParameterStore#storedString()
1586   */
1587  storedString: function () {
1588    // Some browsers automatically unescape characters in the hash, others
1589    // don't. Fortunately, all leave window.location.href alone. So, use that.
1590    var index = window.location.href.indexOf('#');
1591    if (index == -1) {
1592      return '';
1593    }
1594    else {
1595      return window.location.href.substr(index + 1);
1596    }
1597  },
1598
1599  /**
1600   * Checks the hash for changes, and loads Solr parameters from the hash and
1601   * sends a request to Solr if it observes a change or if the hash is empty
1602   */
1603  intervalFunction: function (self) {
1604    return function () {
1605      // Support the back/forward buttons. If the hash changes, do a request.
1606      var hash = self.storedString();
1607      if (self.hash != hash && decodeURIComponent(self.hash) != decodeURIComponent(hash)) {
1608        self.load();
1609        self.manager.doRequest();
1610      }
1611    }
1612  }
1613});
1614
1615}));
1616(function (callback) {
1617  if (typeof define === 'function' && define.amd) {
1618    define(['core/AbstractManager'], callback);
1619  }
1620  else {
1621    callback();
1622  }
1623}(function () {
1624
1625/**
1626 * @see http://wiki.apache.org/solr/SolJSON#JSON_specific_parameters
1627 * @class Manager
1628 * @augments AjaxSolr.AbstractManager
1629 */
1630AjaxSolr.Manager = AjaxSolr.AbstractManager.extend(
1631  /** @lends AjaxSolr.Manager.prototype */
1632  {
1633  executeRequest: function (servlet, string, handler, errorHandler) {
1634    var self = this,
1635        options = {dataType: 'json'};
1636    string = string || this.store.string();
1637    handler = handler || function (data) {
1638      self.handleResponse(data);
1639    };
1640    errorHandler = errorHandler || function (jqXHR, textStatus, errorThrown) {
1641      self.handleError(textStatus + ', ' + errorThrown);
1642    };
1643    if (this.proxyUrl) {
1644      options.url = this.proxyUrl;
1645      options.data = {query: string};
1646      options.type = 'POST';
1647    }
1648    else {
1649      options.url = this.solrUrl + servlet + '?' + string + '&wt=json&json.wrf=?';
1650    }
1651    jQuery.ajax(options).done(handler).fail(errorHandler);
1652  }
1653});
1654
1655}));
1656(function ($) {
1657
1658/**
1659 * A <i>term</i> autocomplete search box, using jQueryUI.autocomplete. This
1660 * implementation uses Solr's facet.prefix technique. This technique benefits
1661 * from honoring the filter query state and by being able to put words prior to
1662 * the last one the user is typing into a filter query as well to get even more
1663 * relevant completion suggestions.
1664 *
1665 * Index instructions:
1666 * 1. Put a facet warming query into Solr's "firstSearcher" in solrconfig.xml,
1667 * for the target field.
1668 * 2. Use appropriate text analysis to include a tokenizer (not keyword) and do
1669 * <i>not</i> do stemming or else you will see stems suggested. A 'light'
1670 * stemmer may produce acceptable stems.
1671 * 3. If you are auto-completing in a search box that would normally be using
1672 * the dismax query parser AND your qf parameter references more than one field,
1673 * then you might want to use a catch-all search field to autocomplete on.
1674 *
1675 * For large indexes, another implementation approach like the Suggester feature
1676 * or TermsComponent might be better than a faceting approach.
1677 *
1678 * Other types of autocomplete (a.k.a. suggest) are "search-results",
1679 * "query-log", and "facet-value". This widget does term autocompletion.
1680 *
1681 * @author David Smiley <david.w.smiley at gmail.com>
1682 */
1683AjaxSolr.AutocompleteTermWidget = AjaxSolr.AbstractTextWidget.extend(
1684  /** @lends AjaxSolr.AutocompleteTermWidget.prototype */
1685  {
1686  /**
1687   * @param {Object} attributes
1688   * @param {String} attributes.field The Solr field to autocomplete indexed
1689   *   terms from.
1690   * @param {Boolean} [attributes.tokenized] Whether the underlying field is
1691   *   tokenized. This component will take words before the last word
1692   *   (whitespace separated) and generate a filter query for those words, while
1693   *   only the last word will be used for facet.prefix. For field-value
1694   *   completion (on just one field) or query log completion, you would have a
1695   *   non-tokenized field to complete against. Defaults to <tt>true</tt>.
1696   * @param {Boolean} [attributes.lowercase] Indicates whether to lowercase the
1697   *   facet.prefix value. Defaults to <tt>true</tt>.
1698   * @param {Number} [attributes.limit] The maximum number of results to show.
1699   *   Defaults to 10.
1700   * @param {Number} [attributes.minLength] The minimum number of characters
1701   *   required to show suggestions. Defaults to 2.
1702   * @param {String} [attributes.servlet] The URL path that follows the solr
1703   *   webapp, for use in auto-complete queries. If not specified, the manager's
1704   *   servlet property will be used. You may prepend the servlet with a core if
1705   *   using multiple cores. It is a good idea to use a non-default one to
1706   *   differentiate these requests in server logs and Solr statistics.
1707   */
1708  constructor: function (attributes) {
1709    AjaxSolr.AutocompleteTermWidget.__super__.constructor.apply(this, arguments);
1710    AjaxSolr.extend(this, {
1711      field: null,
1712      tokenized: true,
1713      lowercase: true,
1714      limit: 10,
1715      minLength: 2,
1716      servlet: null
1717    }, attributes);
1718  },
1719
1720  init: function () {
1721    var self = this;
1722
1723    if (!this.field) {
1724      throw '"field" must be set on AutocompleteTermWidget.';
1725    }
1726    this.servlet = this.servlet || this.manager.servlet;
1727
1728    $(this.target).find('input').bind('keydown', function (e) {
1729      if (e.which == 13) {
1730        var q = $(this).val();
1731        if (self.set(q)) {
1732          self.doRequest();
1733        }
1734      }
1735    });
1736
1737    $(this.target).find('input').autocomplete({
1738      source: function (request, response) { // note: must always call response()
1739        // If term ends with a space:
1740        if (request.term.charAt(request.term.length - 1).replace(/^ +/, '').replace(/ +$/, '') == '') {
1741          response();
1742          return;
1743        }
1744
1745        var term = request.term,
1746            facetPrefix = term, // before the last word (if we tokenize)
1747            fq = '',
1748            store = new AjaxSolr.ParameterStore();
1749
1750        store.addByValue('fq', self.manager.store.values('fq'));
1751
1752        if (self.tokenized) {
1753          // Split out the last word of the term from the words before it.
1754          var lastSpace = term.lastIndexOf(' ');
1755          if (lastSpace > -1) {
1756            fq = term.substring(0, lastSpace);
1757            facetPrefix = term.substring(lastSpace + 1);
1758            store.addByValue('fq', '{!dismax qf=' + self.field + '}' + fq);
1759          }
1760        }
1761        if (self.lowercase) {
1762          facetPrefix = facetPrefix.toLowerCase();
1763        }
1764
1765        store.addByValue('facet.field', self.field);
1766        store.addByValue('facet.limit', self.limit);
1767        store.addByValue('facet.prefix', facetPrefix);
1768
1769        self.manager.executeRequest(self.servlet, 'json.nl=arrarr&q=*:*&rows=0&facet=true&facet.mincount=1&' + store.string(), function (data) {
1770          response($.map(data.facet_counts.facet_fields[self.field], function (term) {
1771            var q = (fq + ' ' + term[0]).replace(/^ +/, '').replace(/ +$/, '');
1772            return {
1773              label: q + ' (' + term[1] + ')',
1774              value: q
1775            }
1776          }));
1777        });
1778      },
1779      minLength: this.minLength,
1780      select: function(event, ui) {
1781        if (self.set(ui.item.value)) {
1782          self.doRequest();
1783        }
1784      }
1785    });
1786  }
1787});
1788
1789})(jQuery);
1790(function (callback) {
1791  if (typeof define === 'function' && define.amd) {
1792    define(['core/AbstractWidget'], callback);
1793  }
1794  else {
1795    callback();
1796  }
1797}(function () {
1798
1799(function ($) {
1800
1801/**
1802 * A pager widget for jQuery.
1803 *
1804 * <p>Heavily inspired by the Ruby on Rails will_paginate gem.</p>
1805 *
1806 * @expects this.target to be a list.
1807 * @class PagerWidget
1808 * @augments AjaxSolr.AbstractWidget
1809 * @todo Don't use the manager to send the request. Request only the results,
1810 * not the facets. Update only itself and the results widget.
1811 */
1812AjaxSolr.PagerWidget = AjaxSolr.AbstractWidget.extend(
1813  /** @lends AjaxSolr.PagerWidget.prototype */
1814  {
1815  /**
1816   * @param {Object} [attributes]
1817   * @param {Number} [attributes.innerWindow] How many links are shown around
1818   *   the current page. Defaults to 4.
1819   * @param {Number} [attributes.outerWindow] How many links are around the
1820   *   first and the last page. Defaults to 1.
1821   * @param {String} [attributes.prevLabel] The previous page link label.
1822   *   Defaults to "&laquo; Previous".
1823   * @param {String} [attributes.nextLabel] The next page link label. Defaults
1824   *   to "Next &raquo;".
1825   * @param {String} [attributes.separator] Separator between pagination links.
1826   *   Defaults to " ".
1827   */
1828  constructor: function (attributes) {
1829    AjaxSolr.PagerWidget.__super__.constructor.apply(this, arguments);
1830    AjaxSolr.extend(this, {
1831      innerWindow: 4,
1832      outerWindow: 1,
1833      prevLabel: '&laquo; Previous',
1834      nextLabel: 'Next &raquo;',
1835      separator: ' ',
1836      // The current page number.
1837      currentPage: null,
1838      // The total number of pages.
1839      totalPages: null
1840    }, attributes);
1841  },
1842
1843  /**
1844   * @returns {String} The gap in page links, which is represented by:
1845   *   <span class="pager-gap">&hellip;</span>
1846   */
1847  gapMarker: function () {
1848    return '<span class="pager-gap">&hellip;</span>';
1849  },
1850
1851  /**
1852   * @returns {Array} The links for the visible page numbers.
1853   */
1854  windowedLinks: function () {
1855    var links = [];
1856
1857    var prev = null;
1858
1859    visible = this.visiblePageNumbers();
1860    for (var i = 0, l = visible.length; i < l; i++) {
1861      if (prev && visible[i] > prev + 1) links.push(this.gapMarker());
1862      links.push(this.pageLinkOrSpan(visible[i], [ 'pager-current' ]));
1863      prev = visible[i];
1864    }
1865
1866    return links;
1867  },
1868
1869  /**
1870   * @returns {Array} The visible page numbers according to the window options.
1871   */
1872  visiblePageNumbers: function () {
1873    var windowFrom = this.currentPage - this.innerWindow;
1874    var windowTo = this.currentPage + this.innerWindow;
1875
1876    // If the window is truncated on one side, make the other side longer
1877    if (windowTo > this.totalPages) {
1878      windowFrom = Math.max(0, windowFrom - (windowTo - this.totalPages));
1879      windowTo = this.totalPages;
1880    }
1881    if (windowFrom < 1) {
1882      windowTo = Math.min(this.totalPages, windowTo + (1 - windowFrom));
1883      windowFrom = 1;
1884    }
1885
1886    var visible = [];
1887
1888    // Always show the first page
1889    visible.push(1);
1890    // Don't add inner window pages twice
1891    for (var i = 2; i <= Math.min(1 + this.outerWindow, windowFrom - 1); i++) {
1892      visible.push(i);
1893    }
1894    // If the gap is just one page, close the gap
1895    if (1 + this.outerWindow == windowFrom - 2) {
1896      visible.push(windowFrom - 1);
1897    }
1898    // Don't add the first or last page twice
1899    for (var i = Math.max(2, windowFrom); i <= Math.min(windowTo, this.totalPages - 1); i++) {
1900      visible.push(i);
1901    }
1902    // If the gap is just one page, close the gap
1903    if (this.totalPages - this.outerWindow == windowTo + 2) {
1904      visible.push(windowTo + 1);
1905    }
1906    // Don't add inner window pages twice
1907    for (var i = Math.max(this.totalPages - this.outerWindow, windowTo + 1); i < this.totalPages; i++) {
1908      visible.push(i);
1909    }
1910    // Always show the last page, unless it's the first page
1911    if (this.totalPages > 1) {
1912      visible.push(this.totalPages);
1913    }
1914
1915    return visible;
1916  },
1917
1918  /**
1919   * @param {Number} page A page number.
1920   * @param {String} classnames CSS classes to add to the page link.
1921   * @param {String} text The inner HTML of the page link (optional).
1922   * @returns The link or span for the given page.
1923   */
1924  pageLinkOrSpan: function (page, classnames, text) {
1925    text = text || page;
1926
1927    if (page && page != this.currentPage) {
1928      return $('<a href="#"></a>').html(text).attr('rel', this.relValue(page)).addClass(classnames[1]).click(this.clickHandler(page));
1929    }
1930    else {
1931      return $('<span></span>').html(text).addClass(classnames.join(' '));
1932    }
1933  },
1934
1935  /**
1936   * @param {Number} page A page number.
1937   * @returns {Function} The click handler for the page link.
1938   */
1939  clickHandler: function (page) {
1940    var self = this;
1941    return function () {
1942      self.manager.store.get('start').val((page - 1) * self.perPage());
1943      self.doRequest();
1944      return false;
1945    }
1946  },
1947
1948  /**
1949   * @param {Number} page A page number.
1950   * @returns {String} The <tt>rel</tt> attribute for the page link.
1951   */
1952  relValue: function (page) {
1953    switch (page) {
1954      case this.previousPage():
1955        return 'prev' + (page == 1 ? 'start' : '');
1956      case this.nextPage():
1957        return 'next';
1958      case 1:
1959        return 'start';
1960      default:
1961        return '';
1962    }
1963  },
1964
1965  /**
1966   * @returns {Number} The page number of the previous page or null if no previous page.
1967   */
1968  previousPage: function () {
1969    return this.currentPage > 1 ? (this.currentPage - 1) : null;
1970  },
1971
1972  /**
1973   * @returns {Number} The page number of the next page or null if no next page.
1974   */
1975  nextPage: function () {
1976    return this.currentPage < this.totalPages ? (this.currentPage + 1) : null;
1977  },
1978
1979  /**
1980   * An abstract hook for child implementations.
1981   *
1982   * @param {Number} perPage The number of items shown per results page.
1983   * @param {Number} offset The index in the result set of the first document to render.
1984   * @param {Number} total The total number of documents in the result set.
1985   */
1986  renderHeader: function (perPage, offset, total) {},
1987
1988  /**
1989   * Render the pagination links.
1990   *
1991   * @param {Array} links The links for the visible page numbers.
1992   */
1993  renderLinks: function (links) {
1994    if (this.totalPages) {
1995      links.unshift(this.pageLinkOrSpan(this.previousPage(), [ 'pager-disabled', 'pager-prev' ], this.prevLabel));
1996      links.push(this.pageLinkOrSpan(this.nextPage(), [ 'pager-disabled', 'pager-next' ], this.nextLabel));
1997
1998      var $target = $(this.target);
1999      $target.empty();
2000
2001      for (var i = 0, l = links.length; i < l; i++) {
2002        var $li = $('<li></li>');
2003        if (this.separator && i > 0) {
2004          $li.append(this.separator);
2005        }
2006        $target.append($li.append(links[i]));
2007      }
2008    }
2009  },
2010
2011  /**
2012   * @returns {Number} The number of results to display per page.
2013   */
2014  perPage: function () {
2015    return parseInt(this.manager.response.responseHeader && this.manager.response.responseHeader.params && this.manager.response.responseHeader.params.rows || this.manager.store.get('rows').val() || 10);
2016  },
2017
2018  /**
2019   * @returns {Number} The Solr offset parameter's value.
2020   */
2021  getOffset: function () {
2022    return parseInt(this.manager.response.responseHeader && this.manager.response.responseHeader.params && this.manager.response.responseHeader.params.start || this.manager.store.get('start').val() || 0);
2023  },
2024
2025  afterRequest: function () {
2026    var perPage = this.perPage();
2027    var offset  = this.getOffset();
2028    var total   = parseInt(this.manager.response.response.numFound);
2029
2030    // Normalize the offset to a multiple of perPage.
2031    offset = offset - offset % perPage;
2032
2033    this.currentPage = Math.ceil((offset + 1) / perPage);
2034    this.totalPages = Math.ceil(total / perPage);
2035
2036    $(this.target).empty();
2037
2038    this.renderLinks(this.windowedLinks());
2039    this.renderHeader(perPage, offset, total);
2040  }
2041});
2042
2043})(jQuery);
2044
2045}));
2046;(function(history) {
2047  /**
2048   * A parameter store that stores the values of exposed parameters in the URL via History.js
2049   * to maintain the application's state. This uses the HTML5 History API for newer browsers, and
2050   * falls back to using the hash in older browsers. Don't forget to add the following (or similar)
2051   * inside your <tt>head</tt> tag:
2052   *
2053   * <pre>
2054   * <script src="history.js/scripts/bundled/html4+html5/jquery.history.js"></script>
2055   * </pre>
2056   *
2057   * Configure the manager with:
2058   *
2059   * <pre>
2060   * Manager.setStore(new AjaxSolr.ParameterHistoryStore());
2061   * </pre>
2062   *
2063   * @class ParameterHistoryStore
2064   * @augments AjaxSolr.ParameterStore
2065   * @see https://github.com/browserstate/history.js
2066   * @see http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html
2067   */
2068  AjaxSolr.ParameterHistoryStore = AjaxSolr.ParameterStore.extend(
2069    /** @lends AjaxSolr.ParameterHistoryStore.prototype */
2070    {
2071    init: function () {
2072      if (this.exposed.length) {
2073        if (!history) {
2074          throw 'ParameterHistoryStore requires History.js';
2075        }
2076
2077        history.Adapter.bind(window, 'statechange', this.stateChangeFunction(this));
2078      }
2079    },
2080
2081    /**
2082     * Stores the values of the exposed parameters in both the local hash and History.js
2083     * No other code should be made to change these two values.
2084     */
2085    save: function () {
2086      this.hash = this.exposedString();
2087      history.pushState({ params: this.hash }, null, '?' + this.hash);
2088    },
2089
2090    /**
2091     * @see ParameterStore#storedString()
2092     */
2093    storedString: function () {
2094      var state = history.getState();
2095
2096      // Load the state from the History object.
2097      if (state.data && state.data.params) {
2098        return state.data.params;
2099      }
2100
2101      // If initial load, load the state from the URL.
2102      var url = state.cleanUrl, index = url.indexOf('?');
2103      if (index == -1) {
2104        return '';
2105      }
2106      else {
2107        return url.substr(index + 1);
2108      }
2109    },
2110
2111    /**
2112     * Called when History.js detects a state change. Checks if state is different to previous state,
2113     * and if so, sends a request to Solr. This needs to check if the state has changed since it also
2114     * gets called when we call pushState above.
2115     */
2116    stateChangeFunction: function (self) {
2117      return function () {
2118        var hash = self.storedString();
2119
2120        if (self.hash != hash) {
2121          self.load();
2122          self.manager.doRequest();
2123        }
2124      }
2125    }
2126  });
2127})(window.History);
2128/**
2129 * A parameter store that stores the values of exposed parameters using the YUI
2130 * History Manager to maintain the application's state. Don't forget to add the
2131 * following inside your <tt>head</tt> tag:
2132 *
2133 * <pre>
2134 * <script src="http://yui.yahooapis.com/2.9.0/build/yahoo/yahoo-min.js"></script>
2135 * <script src="http://yui.yahooapis.com/2.9.0/build/event/event-min.js"></script>
2136 * <script src="http://yui.yahooapis.com/2.9.0/build/history/history-min.js"></script>
2137 * </pre>
2138 *
2139 * And the following inside your <tt>body</tt> tag:
2140 *
2141 * <pre>
2142 * <iframe id="yui-history-iframe" src="path-to-existing-asset" style="position:absolute;top:0;left:0;width:1px;height:1px;visibility:hidden"></iframe>
2143 * <input id="yui-history-field" type="hidden">
2144 * </pre>
2145 *
2146 * Configure the manager with:
2147 *
2148 * <pre>
2149 * Manager.setStore(new AjaxSolr.ParameterYUIStore());
2150 * </pre>
2151 *
2152 * @see http://developer.yahoo.com/yui/history/
2153 * @class ParameterYUIStore
2154 * @augments AjaxSolr.ParameterStore
2155 */
2156AjaxSolr.ParameterYUIStore = AjaxSolr.ParameterStore.extend(
2157  /** @lends AjaxSolr.ParameterYUIStore.prototype */
2158  {
2159  /**
2160   * @param {Object} [attributes]
2161   * @param {String} [attributes.module] The name of the YUI History Manager
2162   *   module to use for the parameter store. Defaults to "q".
2163   */
2164  constructor: function (attributes) {
2165    AjaxSolr.ParameterYUIStore.__super__.constructor.apply(this, arguments);
2166    AjaxSolr.extend(this, {
2167      module: 'q',
2168      // Whether the YUI History Manager is initialized.
2169      initialized: false,
2170      // Whether the parameter store is curring loading state.
2171      loading: false,
2172      // Whether the parameter store is curring saving state.
2173      saving: false
2174    }, attributes);
2175  },
2176
2177  /**
2178   * Initializes the YUI History Manager.
2179   */
2180  init: function () {
2181    if (this.exposed.length) {
2182      var self = this;
2183      YAHOO.util.History.register(this.module, YAHOO.util.History.getBookmarkedState(this.module) || this.exposedString(), function () {
2184        if (!self.saving) {
2185          self.loading = true;
2186          self.load();
2187          self.manager.doRequest();
2188          self.loading = false;
2189        }
2190      });
2191      YAHOO.util.History.onReady(function () {
2192        self.initialized = true;
2193        self.load();
2194        self.manager.doRequest();
2195      });
2196      YAHOO.util.History.initialize('yui-history-field', 'yui-history-iframe');
2197    }
2198  },
2199
2200  /**
2201   * Stores the values of the exposed parameters in the YUI History Manager.
2202   */
2203  save: function () {
2204    if (!self.loading) {
2205      this.saving = true;
2206      YAHOO.util.History.navigate(this.module, this.exposedString());
2207      this.saving = false;
2208    }
2209  },
2210
2211  /**
2212   * @see ParameterStore#storedString()
2213   */
2214  storedString: function () {
2215    return this.initialized ? YAHOO.util.History.getCurrentState(this.module) : this.exposedString();
2216  }
2217});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.