Allow the child to handle the suggestions without parsing the response.
83 | */
84 | handleSuggestions: function () {}
85 | });
86 |
87 | }));
88 |
--------------------------------------------------------------------------------
/widgets/ParameterHistoryStore.js:
--------------------------------------------------------------------------------
1 | ;(function(history) {
2 | /**
3 | * A parameter store that stores the values of exposed parameters in the URL via History.js
4 | * to maintain the application's state. This uses the HTML5 History API for newer browsers, and
5 | * falls back to using the hash in older browsers. Don't forget to add the following (or similar)
6 | * inside your head tag:
7 | *
8 | *
The ParameterHashStore observes the hash for changes and loads Solr
15 | * parameters from the hash if it observes a change or if the hash is empty.
16 | * The onhashchange event is used if the browser supports it.
17 | *
18 | *
Configure the manager with:
19 | *
20 | * @class ParameterHashStore
21 | * @augments AjaxSolr.ParameterStore
22 | * @see https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange
23 | */
24 | AjaxSolr.ParameterHashStore = AjaxSolr.ParameterStore.extend(
25 | /** @lends AjaxSolr.ParameterHashStore.prototype */
26 | {
27 | /**
28 | * @param {Object} [attributes]
29 | * @param {Number} [attributes.interval] The interval in milliseconds to use
30 | * in setInterval(). Do not set the interval too low as you may set
31 | * up a race condition. Defaults to 250.
32 | */
33 | constructor: function (attributes) {
34 | AjaxSolr.ParameterHashStore.__super__.constructor.apply(this, arguments);
35 | AjaxSolr.extend(this, {
36 | interval: 250,
37 | // Reference to the setInterval() function.
38 | intervalId: null,
39 | // A local copy of the URL hash, so we can detect changes to it.
40 | hash: ''
41 | }, attributes);
42 | },
43 |
44 | /**
45 | * If loading and saving the hash take longer than interval, we'll
46 | * hit a race condition. However, this should never happen.
47 | */
48 | init: function () {
49 | if (this.exposed.length) {
50 | // Check if the browser supports the onhashchange event. IE 8 and 9 in compatibility mode
51 | // incorrectly report support for onhashchange.
52 | if ('onhashchange' in window && (!document.documentMode || document.documentMode > 7)) {
53 | if (window.addEventListener) {
54 | window.addEventListener('hashchange', this.intervalFunction(this), false);
55 | }
56 | else if (window.attachEvent) {
57 | window.attachEvent('onhashchange', this.intervalFunction(this));
58 | }
59 | else {
60 | window.onhashchange = this.intervalFunction(this);
61 | }
62 | }
63 | else {
64 | this.intervalId = window.setInterval(this.intervalFunction(this), this.interval);
65 | }
66 | }
67 | },
68 |
69 | /**
70 | * Stores the values of the exposed parameters in both the local hash and the
71 | * URL hash. No other code should be made to change these two values.
72 | */
73 | save: function () {
74 | this.hash = this.exposedString();
75 | if (this.storedString()) {
76 | // Make a new history entry.
77 | window.location.hash = this.hash;
78 | }
79 | else {
80 | // Replace the old history entry.
81 | window.location.replace(window.location.href.replace('#', '') + '#' + this.hash);
82 | }
83 | },
84 |
85 | /**
86 | * @see ParameterStore#storedString()
87 | */
88 | storedString: function () {
89 | // Some browsers automatically unescape characters in the hash, others
90 | // don't. Fortunately, all leave window.location.href alone. So, use that.
91 | var index = window.location.href.indexOf('#');
92 | if (index == -1) {
93 | return '';
94 | }
95 | else {
96 | return window.location.href.substr(index + 1);
97 | }
98 | },
99 |
100 | /**
101 | * Checks the hash for changes, and loads Solr parameters from the hash and
102 | * sends a request to Solr if it observes a change or if the hash is empty
103 | */
104 | intervalFunction: function (self) {
105 | return function () {
106 | // Support the back/forward buttons. If the hash changes, do a request.
107 | var hash = self.storedString();
108 | if (self.hash != hash && decodeURIComponent(self.hash) != decodeURIComponent(hash)) {
109 | self.load();
110 | self.manager.doRequest();
111 | }
112 | }
113 | }
114 | });
115 |
116 | }));
117 |
--------------------------------------------------------------------------------
/core/Core.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace A unique namespace for the AJAX Solr library.
3 | */
4 | AjaxSolr = function () {};
5 |
6 | /**
7 | * @namespace Baseclass for all classes
8 | * @see https://github.com/documentcloud/backbone/blob/51eed189bf4d25877be4acdf51e0a4c6039583c5/backbone.js#L243
9 | */
10 | AjaxSolr.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 | */
22 | AjaxSolr.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 | */
59 | AjaxSolr.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 | */
97 | AjaxSolr.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 | */
114 | AjaxSolr.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 | */
145 | AjaxSolr.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 | */
153 | AjaxSolr.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 | */
161 | AjaxSolr.isString = function (obj) {
162 | return obj != null && typeof obj == 'string';
163 | };
164 |
--------------------------------------------------------------------------------
/core/AbstractManager.js:
--------------------------------------------------------------------------------
1 | (function (callback) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['core/Core'], callback);
4 | }
5 | else {
6 | callback();
7 | }
8 | }(function () {
9 |
10 | /**
11 | * The Manager acts as the controller in a Model-View-Controller framework. All
12 | * public calls should be performed on the manager object.
13 | *
14 | * @param properties A map of fields to set. Refer to the list of public fields.
15 | * @class AbstractManager
16 | */
17 | AjaxSolr.AbstractManager = AjaxSolr.Class.extend(
18 | /** @lends AjaxSolr.AbstractManager.prototype */
19 | {
20 | /**
21 | * @param {Object} [attributes]
22 | * @param {String} [attributes.solrUrl] The fully-qualified URL of the Solr
23 | * application. You must include the trailing slash. Do not include the path
24 | * to any Solr servlet. Defaults to "http://localhost:8983/solr/"
25 | * @param {String} [attributes.proxyUrl] If we want to proxy queries through a
26 | * script, rather than send queries to Solr directly, set this field to the
27 | * fully-qualified URL of the script.
28 | * @param {String} [attributes.servlet] The default Solr servlet. You may
29 | * prepend the servlet with a core if using multiple cores. Defaults to
30 | * "servlet".
31 | */
32 | constructor: function (attributes) {
33 | AjaxSolr.extend(this, {
34 | solrUrl: 'http://localhost:8983/solr/',
35 | proxyUrl: null,
36 | servlet: 'select',
37 | // The most recent response from Solr.
38 | response: {},
39 | // A collection of all registered widgets.
40 | widgets: {},
41 | // The parameter store for the manager and its widgets.
42 | store: null,
43 | // Whether init() has been called yet.
44 | initialized: false
45 | }, attributes);
46 | },
47 |
48 | /**
49 | * An abstract hook for child implementations.
50 | *
51 | *
This method should be called after the store and the widgets have been
52 | * added. It should initialize the widgets and the store, and do any other
53 | * one-time initializations, e.g., perform the first request to Solr.
54 | *
55 | *
If no store has been set, it sets the store to the basic
56 | * AjaxSolr.ParameterStore.
57 | */
58 | init: function () {
59 | this.initialized = true;
60 | if (this.store === null) {
61 | this.setStore(new AjaxSolr.ParameterStore());
62 | }
63 | this.store.load(false);
64 | for (var widgetId in this.widgets) {
65 | this.widgets[widgetId].init();
66 | }
67 | this.store.init();
68 | },
69 |
70 | /**
71 | * Set the manager's parameter store.
72 | *
73 | * @param {AjaxSolr.ParameterStore} store
74 | */
75 | setStore: function (store) {
76 | store.manager = this;
77 | this.store = store;
78 | },
79 |
80 | /**
81 | * Adds a widget to the manager.
82 | *
83 | * @param {AjaxSolr.AbstractWidget} widget
84 | */
85 | addWidget: function (widget) {
86 | widget.manager = this;
87 | this.widgets[widget.id] = widget;
88 | },
89 |
90 | /**
91 | * Stores the Solr parameters to be sent to Solr and sends a request to Solr.
92 | *
93 | * @param {Boolean} [start] The Solr start offset parameter.
94 | * @param {String} [servlet] The Solr servlet to send the request to.
95 | */
96 | doRequest: function (start, servlet) {
97 | if (this.initialized === false) {
98 | this.init();
99 | }
100 | // Allow non-pagination widgets to reset the offset parameter.
101 | if (start !== undefined) {
102 | this.store.get('start').val(start);
103 | }
104 | if (servlet === undefined) {
105 | servlet = this.servlet;
106 | }
107 |
108 | this.store.save();
109 |
110 | for (var widgetId in this.widgets) {
111 | this.widgets[widgetId].beforeRequest();
112 | }
113 |
114 | this.executeRequest(servlet);
115 | },
116 |
117 | /**
118 | * An abstract hook for child implementations.
119 | *
120 | *
Sends the request to Solr, i.e. to this.solrUrl or
121 | * this.proxyUrl, and receives Solr's response. It should pass Solr's
122 | * response to handleResponse() for handling.
123 | *
124 | *
See managers/Manager.jquery.js for a jQuery implementation.
125 | *
126 | * @param {String} servlet The Solr servlet to send the request to.
127 | * @param {String} string The query string of the request. If not set, it
128 | * should default to this.store.string()
129 | * @throws If not defined in child implementation.
130 | */
131 | executeRequest: function (servlet, string) {
132 | throw 'Abstract method executeRequest must be overridden in a subclass.';
133 | },
134 |
135 | /**
136 | * This method is executed after the Solr response data arrives. Allows each
137 | * widget to handle Solr's response separately.
138 | *
139 | * @param {Object} data The Solr response.
140 | */
141 | handleResponse: function (data) {
142 | this.response = data;
143 |
144 | for (var widgetId in this.widgets) {
145 | this.widgets[widgetId].afterRequest();
146 | }
147 | },
148 |
149 | /**
150 | * This method is executed if Solr encounters an error.
151 | *
152 | * @param {String} message An error message.
153 | */
154 | handleError: function (message) {
155 | window.console && console.log && console.log(message);
156 | }
157 | });
158 |
159 | }));
160 |
--------------------------------------------------------------------------------
/core/Parameter.js:
--------------------------------------------------------------------------------
1 | (function (callback) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['core/Core'], callback);
4 | }
5 | else {
6 | callback();
7 | }
8 | }(function () {
9 |
10 | /**
11 | * Represents a Solr parameter.
12 | *
13 | * @param properties A map of fields to set. Refer to the list of public fields.
14 | * @class Parameter
15 | */
16 | AjaxSolr.Parameter = AjaxSolr.Class.extend(
17 | /** @lends AjaxSolr.Parameter.prototype */
18 | {
19 | /**
20 | * @param {Object} attributes
21 | * @param {String} attributes.name The parameter's name.
22 | * @param {String} [attributes.value] The parameter's value.
23 | * @param {Object} [attributes.local] The parameter's local parameters.
24 | */
25 | constructor: function (attributes) {
26 | AjaxSolr.extend(this, {
27 | name: null,
28 | value: null,
29 | locals: {}
30 | }, attributes);
31 | },
32 |
33 | /**
34 | * Returns the value. If called with an argument, sets the value.
35 | *
36 | * @param {String|Number|String[]|Number[]} [value] The value to set.
37 | * @returns The value.
38 | */
39 | val: function (value) {
40 | if (value === undefined) {
41 | return this.value;
42 | }
43 | else {
44 | this.value = value;
45 | }
46 | },
47 |
48 | /**
49 | * Returns the value of a local parameter. If called with a second argument,
50 | * sets the value of a local parameter.
51 | *
52 | * @param {String} name The name of the local parameter.
53 | * @param {String|Number|String[]|Number[]} [value] The value to set.
54 | * @returns The value.
55 | */
56 | local: function (name, value) {
57 | if (value === undefined) {
58 | return this.locals[name];
59 | }
60 | else {
61 | this.locals[name] = value;
62 | }
63 | },
64 |
65 | /**
66 | * Deletes a local parameter.
67 | *
68 | * @param {String} name The name of the local parameter.
69 | */
70 | remove: function (name) {
71 | delete this.locals[name];
72 | },
73 |
74 | /**
75 | * Returns the Solr parameter as a query string key-value pair.
76 | *
77 | *
IE6 calls the default toString() if you write store.toString()
78 | * . So, we need to choose another name for toString().
79 | */
80 | string: function () {
81 | var pairs = [];
82 |
83 | for (var name in this.locals) {
84 | if (this.locals[name]) {
85 | pairs.push(name + '=' + encodeURIComponent(this.locals[name]));
86 | }
87 | }
88 |
89 | var prefix = pairs.length ? '{!' + pairs.join('%20') + '}' : '';
90 |
91 | if (this.value) {
92 | return this.name + '=' + prefix + this.valueString(this.value);
93 | }
94 | // For dismax request handlers, if the q parameter has local params, the
95 | // q parameter must be set to a non-empty value. In case the q parameter
96 | // has local params but is empty, use the q.alt parameter, which accepts
97 | // wildcards.
98 | else if (this.name == 'q' && prefix) {
99 | return 'q.alt=' + prefix + encodeURIComponent('*:*');
100 | }
101 | else {
102 | return '';
103 | }
104 | },
105 |
106 | /**
107 | * Parses a string formed by calling string().
108 | *
109 | * @param {String} str The string to parse.
110 | */
111 | parseString: function (str) {
112 | var param = str.match(/^([^=]+)=(?:\{!([^\}]*)\})?(.*)$/);
113 | if (param) {
114 | var matches;
115 |
116 | while (matches = /([^\s=]+)=(\S*)/g.exec(decodeURIComponent(param[2]))) {
117 | this.locals[matches[1]] = decodeURIComponent(matches[2]);
118 | param[2] = param[2].replace(matches[0], ''); // Safari's exec seems not to do this on its own
119 | }
120 |
121 | if (param[1] == 'q.alt') {
122 | this.name = 'q';
123 | // if q.alt is present, assume it is because q was empty, as above
124 | }
125 | else {
126 | this.name = param[1];
127 | this.value = this.parseValueString(param[3]);
128 | }
129 | }
130 | },
131 |
132 | /**
133 | * Returns the value as a URL-encoded string.
134 | *
135 | * @private
136 | * @param {String|Number|String[]|Number[]} value The value.
137 | * @returns {String} The URL-encoded string.
138 | */
139 | valueString: function (value) {
140 | value = AjaxSolr.isArray(value) ? value.join(',') : value;
141 | return encodeURIComponent(value);
142 | },
143 |
144 | /**
145 | * Parses a URL-encoded string to return the value.
146 | *
147 | * @private
148 | * @param {String} str The URL-encoded string.
149 | * @returns {Array} The value.
150 | */
151 | parseValueString: function (str) {
152 | str = decodeURIComponent(str);
153 | return str.indexOf(',') == -1 ? str : str.split(',');
154 | }
155 | });
156 |
157 | /**
158 | * Escapes a value, to be used in, for example, an fq parameter. Surrounds
159 | * strings containing spaces or colons in double quotes.
160 | *
161 | * @public
162 | * @param {String|Number} value The value.
163 | * @returns {String} The escaped value.
164 | */
165 | AjaxSolr.Parameter.escapeValue = function (value) {
166 | // If the field value has a space, colon, quotation mark or forward slash
167 | // in it, wrap it in quotes, unless it is a range query or it is already
168 | // wrapped in quotes.
169 | if (value.match(/[ :\/"]/) && !value.match(/[\[\{]\S+ TO \S+[\]\}]/) && !value.match(/^["\(].*["\)]$/)) {
170 | return '"' + value.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
171 | }
172 | return value;
173 | }
174 |
175 | }));
176 |
--------------------------------------------------------------------------------
/widgets/jquery/AutocompleteTermWidget.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 |
3 | /**
4 | * A term 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 | * not 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
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 true.
41 | * @param {Boolean} [attributes.lowercase] Indicates whether to lowercase the
42 | * facet.prefix value. Defaults to true.
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.AutocompleteTermWidget.__super__.constructor.apply(this, arguments);
55 | AjaxSolr.extend(this, {
56 | field: null,
57 | tokenized: true,
58 | lowercase: true,
59 | limit: 10,
60 | minLength: 2,
61 | servlet: null
62 | }, attributes);
63 | },
64 |
65 | init: function () {
66 | var self = this;
67 |
68 | if (!this.field) {
69 | throw '"field" must be set on AutocompleteTermWidget.';
70 | }
71 | this.servlet = this.servlet || this.manager.servlet;
72 |
73 | $(this.target).find('input').bind('keydown', function (e) {
74 | if (e.which == 13) {
75 | var q = $(this).val();
76 | if (self.set(q)) {
77 | self.doRequest();
78 | }
79 | }
80 | });
81 |
82 | $(this.target).find('input').autocomplete({
83 | source: function (request, response) { // note: must always call response()
84 | // If term ends with a space:
85 | if (request.term.charAt(request.term.length - 1).replace(/^ +/, '').replace(/ +$/, '') == '') {
86 | response();
87 | return;
88 | }
89 |
90 | var term = request.term,
91 | facetPrefix = term, // before the last word (if we tokenize)
92 | fq = '',
93 | store = new AjaxSolr.ParameterStore();
94 |
95 | store.addByValue('fq', self.manager.store.values('fq'));
96 |
97 | if (self.tokenized) {
98 | // Split out the last word of the term from the words before it.
99 | var lastSpace = term.lastIndexOf(' ');
100 | if (lastSpace > -1) {
101 | fq = term.substring(0, lastSpace);
102 | facetPrefix = term.substring(lastSpace + 1);
103 | store.addByValue('fq', '{!dismax qf=' + self.field + '}' + fq);
104 | }
105 | }
106 | if (self.lowercase) {
107 | facetPrefix = facetPrefix.toLowerCase();
108 | }
109 |
110 | store.addByValue('facet.field', self.field);
111 | store.addByValue('facet.limit', self.limit);
112 | store.addByValue('facet.prefix', facetPrefix);
113 |
114 | self.manager.executeRequest(self.servlet, 'json.nl=arrarr&q=*:*&rows=0&facet=true&facet.mincount=1&' + store.string(), function (data) {
115 | response($.map(data.facet_counts.facet_fields[self.field], function (term) {
116 | var q = (fq + ' ' + term[0]).replace(/^ +/, '').replace(/ +$/, '');
117 | return {
118 | label: q + ' (' + term[1] + ')',
119 | value: q
120 | }
121 | }));
122 | });
123 | },
124 | minLength: this.minLength,
125 | select: function(event, ui) {
126 | if (self.set(ui.item.value)) {
127 | self.doRequest();
128 | }
129 | }
130 | });
131 | }
132 | });
133 |
134 | })(jQuery);
135 |
--------------------------------------------------------------------------------
/widgets/jquery/PagerWidget.js:
--------------------------------------------------------------------------------
1 | (function (callback) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['core/AbstractWidget'], callback);
4 | }
5 | else {
6 | callback();
7 | }
8 | }(function () {
9 |
10 | (function ($) {
11 |
12 | /**
13 | * A pager widget for jQuery.
14 | *
15 | *
Heavily inspired by the Ruby on Rails will_paginate gem.
16 | *
17 | * @expects this.target to be a list.
18 | * @class PagerWidget
19 | * @augments AjaxSolr.AbstractWidget
20 | * @todo Don't use the manager to send the request. Request only the results,
21 | * not the facets. Update only itself and the results widget.
22 | */
23 | AjaxSolr.PagerWidget = AjaxSolr.AbstractWidget.extend(
24 | /** @lends AjaxSolr.PagerWidget.prototype */
25 | {
26 | /**
27 | * @param {Object} [attributes]
28 | * @param {Number} [attributes.innerWindow] How many links are shown around
29 | * the current page. Defaults to 4.
30 | * @param {Number} [attributes.outerWindow] How many links are around the
31 | * first and the last page. Defaults to 1.
32 | * @param {String} [attributes.prevLabel] The previous page link label.
33 | * Defaults to "« Previous".
34 | * @param {String} [attributes.nextLabel] The next page link label. Defaults
35 | * to "Next »".
36 | * @param {String} [attributes.separator] Separator between pagination links.
37 | * Defaults to " ".
38 | */
39 | constructor: function (attributes) {
40 | AjaxSolr.PagerWidget.__super__.constructor.apply(this, arguments);
41 | AjaxSolr.extend(this, {
42 | innerWindow: 4,
43 | outerWindow: 1,
44 | prevLabel: '« Previous',
45 | nextLabel: 'Next »',
46 | separator: ' ',
47 | // The current page number.
48 | currentPage: null,
49 | // The total number of pages.
50 | totalPages: null
51 | }, attributes);
52 | },
53 |
54 | /**
55 | * @returns {String} The gap in page links, which is represented by:
56 | * …
57 | */
58 | gapMarker: function () {
59 | return '…';
60 | },
61 |
62 | /**
63 | * @returns {Array} The links for the visible page numbers.
64 | */
65 | windowedLinks: function () {
66 | var links = [];
67 |
68 | var prev = null;
69 |
70 | visible = this.visiblePageNumbers();
71 | for (var i = 0, l = visible.length; i < l; i++) {
72 | if (prev && visible[i] > prev + 1) links.push(this.gapMarker());
73 | links.push(this.pageLinkOrSpan(visible[i], [ 'pager-current' ]));
74 | prev = visible[i];
75 | }
76 |
77 | return links;
78 | },
79 |
80 | /**
81 | * @returns {Array} The visible page numbers according to the window options.
82 | */
83 | visiblePageNumbers: function () {
84 | var windowFrom = this.currentPage - this.innerWindow;
85 | var windowTo = this.currentPage + this.innerWindow;
86 |
87 | // If the window is truncated on one side, make the other side longer
88 | if (windowTo > this.totalPages) {
89 | windowFrom = Math.max(0, windowFrom - (windowTo - this.totalPages));
90 | windowTo = this.totalPages;
91 | }
92 | if (windowFrom < 1) {
93 | windowTo = Math.min(this.totalPages, windowTo + (1 - windowFrom));
94 | windowFrom = 1;
95 | }
96 |
97 | var visible = [];
98 |
99 | // Always show the first page
100 | visible.push(1);
101 | // Don't add inner window pages twice
102 | for (var i = 2; i <= Math.min(1 + this.outerWindow, windowFrom - 1); i++) {
103 | visible.push(i);
104 | }
105 | // If the gap is just one page, close the gap
106 | if (1 + this.outerWindow == windowFrom - 2) {
107 | visible.push(windowFrom - 1);
108 | }
109 | // Don't add the first or last page twice
110 | for (var i = Math.max(2, windowFrom); i <= Math.min(windowTo, this.totalPages - 1); i++) {
111 | visible.push(i);
112 | }
113 | // If the gap is just one page, close the gap
114 | if (this.totalPages - this.outerWindow == windowTo + 2) {
115 | visible.push(windowTo + 1);
116 | }
117 | // Don't add inner window pages twice
118 | for (var i = Math.max(this.totalPages - this.outerWindow, windowTo + 1); i < this.totalPages; i++) {
119 | visible.push(i);
120 | }
121 | // Always show the last page, unless it's the first page
122 | if (this.totalPages > 1) {
123 | visible.push(this.totalPages);
124 | }
125 |
126 | return visible;
127 | },
128 |
129 | /**
130 | * @param {Number} page A page number.
131 | * @param {String} classnames CSS classes to add to the page link.
132 | * @param {String} text The inner HTML of the page link (optional).
133 | * @returns The link or span for the given page.
134 | */
135 | pageLinkOrSpan: function (page, classnames, text) {
136 | text = text || page;
137 |
138 | if (page && page != this.currentPage) {
139 | return $('').html(text).attr('rel', this.relValue(page)).addClass(classnames[1]).click(this.clickHandler(page));
140 | }
141 | else {
142 | return $('').html(text).addClass(classnames.join(' '));
143 | }
144 | },
145 |
146 | /**
147 | * @param {Number} page A page number.
148 | * @returns {Function} The click handler for the page link.
149 | */
150 | clickHandler: function (page) {
151 | var self = this;
152 | return function () {
153 | self.manager.store.get('start').val((page - 1) * self.perPage());
154 | self.doRequest();
155 | return false;
156 | }
157 | },
158 |
159 | /**
160 | * @param {Number} page A page number.
161 | * @returns {String} The rel attribute for the page link.
162 | */
163 | relValue: function (page) {
164 | switch (page) {
165 | case this.previousPage():
166 | return 'prev' + (page == 1 ? 'start' : '');
167 | case this.nextPage():
168 | return 'next';
169 | case 1:
170 | return 'start';
171 | default:
172 | return '';
173 | }
174 | },
175 |
176 | /**
177 | * @returns {Number} The page number of the previous page or null if no previous page.
178 | */
179 | previousPage: function () {
180 | return this.currentPage > 1 ? (this.currentPage - 1) : null;
181 | },
182 |
183 | /**
184 | * @returns {Number} The page number of the next page or null if no next page.
185 | */
186 | nextPage: function () {
187 | return this.currentPage < this.totalPages ? (this.currentPage + 1) : null;
188 | },
189 |
190 | /**
191 | * An abstract hook for child implementations.
192 | *
193 | * @param {Number} perPage The number of items shown per results page.
194 | * @param {Number} offset The index in the result set of the first document to render.
195 | * @param {Number} total The total number of documents in the result set.
196 | */
197 | renderHeader: function (perPage, offset, total) {},
198 |
199 | /**
200 | * Render the pagination links.
201 | *
202 | * @param {Array} links The links for the visible page numbers.
203 | */
204 | renderLinks: function (links) {
205 | if (this.totalPages) {
206 | links.unshift(this.pageLinkOrSpan(this.previousPage(), [ 'pager-disabled', 'pager-prev' ], this.prevLabel));
207 | links.push(this.pageLinkOrSpan(this.nextPage(), [ 'pager-disabled', 'pager-next' ], this.nextLabel));
208 |
209 | var $target = $(this.target);
210 | $target.empty();
211 |
212 | for (var i = 0, l = links.length; i < l; i++) {
213 | var $li = $('');
214 | if (this.separator && i > 0) {
215 | $li.append(this.separator);
216 | }
217 | $target.append($li.append(links[i]));
218 | }
219 | }
220 | },
221 |
222 | /**
223 | * @returns {Number} The number of results to display per page.
224 | */
225 | perPage: function () {
226 | return parseInt(this.manager.response.responseHeader && this.manager.response.responseHeader.params && this.manager.response.responseHeader.params.rows || this.manager.store.get('rows').val() || 10);
227 | },
228 |
229 | /**
230 | * @returns {Number} The Solr offset parameter's value.
231 | */
232 | getOffset: function () {
233 | return parseInt(this.manager.response.responseHeader && this.manager.response.responseHeader.params && this.manager.response.responseHeader.params.start || this.manager.store.get('start').val() || 0);
234 | },
235 |
236 | afterRequest: function () {
237 | var perPage = this.perPage();
238 | var offset = this.getOffset();
239 | var total = parseInt(this.manager.response.response.numFound);
240 |
241 | // Normalize the offset to a multiple of perPage.
242 | offset = offset - offset % perPage;
243 |
244 | this.currentPage = Math.ceil((offset + 1) / perPage);
245 | this.totalPages = Math.ceil(total / perPage);
246 |
247 | $(this.target).empty();
248 |
249 | this.renderLinks(this.windowedLinks());
250 | this.renderHeader(perPage, offset, total);
251 | }
252 | });
253 |
254 | })(jQuery);
255 |
256 | }));
257 |
--------------------------------------------------------------------------------
/core/AbstractFacetWidget.js:
--------------------------------------------------------------------------------
1 | (function (callback) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['core/AbstractWidget', 'core/Parameter'], callback);
4 | }
5 | else {
6 | callback();
7 | }
8 | }(function () {
9 |
10 | /**
11 | * Baseclass for all facet widgets.
12 | *
13 | * @class AbstractFacetWidget
14 | * @augments AjaxSolr.AbstractWidget
15 | */
16 | AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend(
17 | /** @lends AjaxSolr.AbstractFacetWidget.prototype */
18 | {
19 | /**
20 | * @param {Object} attributes
21 | * @param {String} attributes.field The field to facet on.
22 | * @param {Number} [attributes.start] This widget will by default set the
23 | * offset parameter to 0 on each request.
24 | * @param {Boolean} [attributes.multivalue] Set to false to force a
25 | * single "fq" parameter for this widget. Defaults to true.
26 | */
27 | constructor: function (attributes) {
28 | AjaxSolr.AbstractFacetWidget.__super__.constructor.apply(this, arguments);
29 | AjaxSolr.extend(this, {
30 | start: 0,
31 | field: null,
32 | multivalue: true
33 | }, attributes);
34 | },
35 |
36 | init: function () {
37 | this.initStore();
38 | },
39 |
40 | /**
41 | * Add facet parameters to the parameter store.
42 | */
43 | initStore: function () {
44 | /* http://wiki.apache.org/solr/SimpleFacetParameters */
45 | var parameters = [
46 | 'facet.prefix',
47 | 'facet.sort',
48 | 'facet.limit',
49 | 'facet.offset',
50 | 'facet.mincount',
51 | 'facet.missing',
52 | 'facet.method',
53 | 'facet.enum.cache.minDf'
54 | ];
55 |
56 | this.manager.store.addByValue('facet', true);
57 |
58 | // Set facet.field, facet.date or facet.range to truthy values to add
59 | // related per-field parameters to the parameter store.
60 | if (this['facet.field'] !== undefined) {
61 | this.manager.store.addByValue('facet.field', this.field);
62 | }
63 | else if (this['facet.date'] !== undefined) {
64 | this.manager.store.addByValue('facet.date', this.field);
65 | parameters = parameters.concat([
66 | 'facet.date.start',
67 | 'facet.date.end',
68 | 'facet.date.gap',
69 | 'facet.date.hardend',
70 | 'facet.date.other',
71 | 'facet.date.include'
72 | ]);
73 | }
74 | else if (this['facet.range'] !== undefined) {
75 | this.manager.store.addByValue('facet.range', this.field);
76 | parameters = parameters.concat([
77 | 'facet.range.start',
78 | 'facet.range.end',
79 | 'facet.range.gap',
80 | 'facet.range.hardend',
81 | 'facet.range.other',
82 | 'facet.range.include'
83 | ]);
84 | }
85 |
86 | for (var i = 0, l = parameters.length; i < l; i++) {
87 | if (this[parameters[i]] !== undefined) {
88 | this.manager.store.addByValue('f.' + this.field + '.' + parameters[i], this[parameters[i]]);
89 | }
90 | }
91 | },
92 |
93 | /**
94 | * @returns {Boolean} Whether any filter queries have been set using this
95 | * widget's facet field.
96 | */
97 | isEmpty: function () {
98 | return !this.manager.store.find('fq', new RegExp('^-?' + this.field + ':'));
99 | },
100 |
101 | /**
102 | * Sets the filter query.
103 | *
104 | * @returns {Boolean} Whether the selection changed.
105 | */
106 | set: function (value) {
107 | return this.changeSelection(function () {
108 | var a = this.manager.store.removeByValue('fq', new RegExp('^-?' + this.field + ':')),
109 | b = this.manager.store.addByValue('fq', this.fq(value));
110 | return a || b;
111 | });
112 | },
113 |
114 | /**
115 | * Adds a filter query.
116 | *
117 | * @returns {Boolean} Whether a filter query was added.
118 | */
119 | add: function (value) {
120 | return this.changeSelection(function () {
121 | return this.manager.store.addByValue('fq', this.fq(value));
122 | });
123 | },
124 |
125 | /**
126 | * Removes a filter query.
127 | *
128 | * @returns {Boolean} Whether a filter query was removed.
129 | */
130 | remove: function (value) {
131 | return this.changeSelection(function () {
132 | return this.manager.store.removeByValue('fq', this.fq(value));
133 | });
134 | },
135 |
136 | /**
137 | * Removes all filter queries using the widget's facet field.
138 | *
139 | * @returns {Boolean} Whether a filter query was removed.
140 | */
141 | clear: function () {
142 | return this.changeSelection(function () {
143 | return this.manager.store.removeByValue('fq', new RegExp('^-?' + this.field + ':'));
144 | });
145 | },
146 |
147 | /**
148 | * Helper for selection functions.
149 | *
150 | * @param {Function} Selection function to call.
151 | * @returns {Boolean} Whether the selection changed.
152 | */
153 | changeSelection: function (func) {
154 | changed = func.apply(this);
155 | if (changed) {
156 | this.afterChangeSelection();
157 | }
158 | return changed;
159 | },
160 |
161 | /**
162 | * An abstract hook for child implementations.
163 | *
164 | *
This method is executed after the filter queries change.
165 | */
166 | afterChangeSelection: function () {},
167 |
168 | /**
169 | * One of "facet.field", "facet.date" or "facet.range" must be set on the
170 | * widget in order to determine where the facet counts are stored.
171 | *
172 | * @returns {Array} An array of objects with the properties facet and
173 | * count, e.g { facet: 'facet', count: 1 }.
174 | */
175 | getFacetCounts: function () {
176 | var property;
177 | if (this['facet.field'] !== undefined) {
178 | property = 'facet_fields';
179 | }
180 | else if (this['facet.date'] !== undefined) {
181 | property = 'facet_dates';
182 | }
183 | else if (this['facet.range'] !== undefined) {
184 | property = 'facet_ranges';
185 | }
186 | if (property !== undefined) {
187 | switch (this.manager.store.get('json.nl').val()) {
188 | case 'map':
189 | return this.getFacetCountsMap(property);
190 | case 'arrarr':
191 | return this.getFacetCountsArrarr(property);
192 | default:
193 | return this.getFacetCountsFlat(property);
194 | }
195 | }
196 | 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".';
197 | },
198 |
199 | /**
200 | * Used if the facet counts are represented as a JSON object.
201 | *
202 | * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
203 | * @returns {Array} An array of objects with the properties facet and
204 | * count, e.g { facet: 'facet', count: 1 }.
205 | */
206 | getFacetCountsMap: function (property) {
207 | var counts = [];
208 | for (var facet in this.manager.response.facet_counts[property][this.field]) {
209 | counts.push({
210 | facet: facet,
211 | count: parseInt(this.manager.response.facet_counts[property][this.field][facet])
212 | });
213 | }
214 | return counts;
215 | },
216 |
217 | /**
218 | * Used if the facet counts are represented as an array of two-element arrays.
219 | *
220 | * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
221 | * @returns {Array} An array of objects with the properties facet and
222 | * count, e.g { facet: 'facet', count: 1 }.
223 | */
224 | getFacetCountsArrarr: function (property) {
225 | var counts = [];
226 | for (var i = 0, l = this.manager.response.facet_counts[property][this.field].length; i < l; i++) {
227 | counts.push({
228 | facet: this.manager.response.facet_counts[property][this.field][i][0],
229 | count: parseInt(this.manager.response.facet_counts[property][this.field][i][1])
230 | });
231 | }
232 | return counts;
233 | },
234 |
235 | /**
236 | * Used if the facet counts are represented as a flat array.
237 | *
238 | * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
239 | * @returns {Array} An array of objects with the properties facet and
240 | * count, e.g { facet: 'facet', count: 1 }.
241 | */
242 | getFacetCountsFlat: function (property) {
243 | var counts = [];
244 | for (var i = 0, l = this.manager.response.facet_counts[property][this.field].length; i < l; i += 2) {
245 | counts.push({
246 | facet: this.manager.response.facet_counts[property][this.field][i],
247 | count: parseInt(this.manager.response.facet_counts[property][this.field][i+1])
248 | });
249 | }
250 | return counts;
251 | },
252 |
253 | /**
254 | * @param {String} value The value.
255 | * @returns {Function} Sends a request to Solr if it successfully adds a
256 | * filter query with the given value.
257 | */
258 | clickHandler: function (value) {
259 | var self = this, meth = this.multivalue ? 'add' : 'set';
260 | return function () {
261 | if (self[meth].call(self, value)) {
262 | self.doRequest();
263 | }
264 | return false;
265 | }
266 | },
267 |
268 | /**
269 | * @param {String} value The value.
270 | * @returns {Function} Sends a request to Solr if it successfully removes a
271 | * filter query with the given value.
272 | */
273 | unclickHandler: function (value) {
274 | var self = this;
275 | return function () {
276 | if (self.remove(value)) {
277 | self.doRequest();
278 | }
279 | return false;
280 | }
281 | },
282 |
283 | /**
284 | * @param {String} value The facet value.
285 | * @param {Boolean} exclude Whether to exclude this fq parameter value.
286 | * @returns {String} An fq parameter value.
287 | */
288 | fq: function (value, exclude) {
289 | return (exclude ? '-' : '') + this.field + ':' + AjaxSolr.Parameter.escapeValue(value);
290 | }
291 | });
292 |
293 | }));
294 |
--------------------------------------------------------------------------------
/core/ParameterStore.js:
--------------------------------------------------------------------------------
1 | (function (callback) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['core/Core', 'core/Parameter'], callback);
4 | }
5 | else {
6 | callback();
7 | }
8 | }(function () {
9 |
10 | /**
11 | * The ParameterStore, as its name suggests, stores Solr parameters. Widgets
12 | * expose some of these parameters to the user. Whenever the user changes the
13 | * values of these parameters, the state of the application changes. In order to
14 | * allow the user to move back and forth between these states with the browser's
15 | * Back and Forward buttons, and to bookmark these states, each state needs to
16 | * be stored. The easiest method is to store the exposed parameters in the URL
17 | * hash (see the ParameterHashStore class). However, you may implement
18 | * your own storage method by extending this class.
19 | *
20 | *
For a list of possible parameters, please consult the links below.
21 | *
22 | * @see http://wiki.apache.org/solr/CoreQueryParameters
23 | * @see http://wiki.apache.org/solr/CommonQueryParameters
24 | * @see http://wiki.apache.org/solr/SimpleFacetParameters
25 | * @see http://wiki.apache.org/solr/HighlightingParameters
26 | * @see http://wiki.apache.org/solr/MoreLikeThis
27 | * @see http://wiki.apache.org/solr/SpellCheckComponent
28 | * @see http://wiki.apache.org/solr/StatsComponent
29 | * @see http://wiki.apache.org/solr/TermsComponent
30 | * @see http://wiki.apache.org/solr/TermVectorComponent
31 | * @see http://wiki.apache.org/solr/LocalParams
32 | *
33 | * @param properties A map of fields to set. Refer to the list of public fields.
34 | * @class ParameterStore
35 | */
36 | AjaxSolr.ParameterStore = AjaxSolr.Class.extend(
37 | /** @lends AjaxSolr.ParameterStore.prototype */
38 | {
39 | constructor: function (attributes) {
40 | /**
41 | * @param {Object} [attributes]
42 | * @param {String[]} [attributes.exposed] The names of the exposed
43 | * parameters. Any parameters that your widgets expose to the user,
44 | * directly or indirectly, should be listed here.
45 | */
46 | AjaxSolr.extend(this, {
47 | exposed: [],
48 | // The Solr parameters.
49 | params: {},
50 | // A reference to the parameter store's manager.
51 | manager: null
52 | }, attributes);
53 | },
54 |
55 | /**
56 | * An abstract hook for child implementations.
57 | *
58 | *
This method should do any necessary one-time initializations.
59 | */
60 | init: function () {},
61 |
62 | /**
63 | * Some Solr parameters may be specified multiple times. It is easiest to
64 | * hard-code a list of such parameters. You may change the list by passing
65 | * { multiple: /pattern/ } as an argument to the constructor of
66 | * this class or one of its children, e.g.:
67 | *
68 | *
new ParameterStore({ multiple: /pattern/ })
69 | *
70 | * @param {String} name The name of the parameter.
71 | * @returns {Boolean} Whether the parameter may be specified multiple times.
72 | * @see http://lucene.apache.org/solr/api/org/apache/solr/handler/DisMaxRequestHandler.html
73 | */
74 | isMultiple: function (name) {
75 | 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)$/);
76 | },
77 |
78 | /**
79 | * Returns a parameter. If the parameter doesn't exist, creates it.
80 | *
81 | * @param {String} name The name of the parameter.
82 | * @returns {AjaxSolr.Parameter|AjaxSolr.Parameter[]} The parameter.
83 | */
84 | get: function (name) {
85 | if (this.params[name] === undefined) {
86 | var param = new AjaxSolr.Parameter({ name: name });
87 | if (this.isMultiple(name)) {
88 | this.params[name] = [ param ];
89 | }
90 | else {
91 | this.params[name] = param;
92 | }
93 | }
94 | return this.params[name];
95 | },
96 |
97 | /**
98 | * If the parameter may be specified multiple times, returns the values of
99 | * all identically-named parameters. If the parameter may be specified only
100 | * once, returns the value of that parameter.
101 | *
102 | * @param {String} name The name of the parameter.
103 | * @returns {String[]|Number[]} The value(s) of the parameter.
104 | */
105 | values: function (name) {
106 | if (this.params[name] !== undefined) {
107 | if (this.isMultiple(name)) {
108 | var values = [];
109 | for (var i = 0, l = this.params[name].length; i < l; i++) {
110 | values.push(this.params[name][i].val());
111 | }
112 | return values;
113 | }
114 | else {
115 | return [ this.params[name].val() ];
116 | }
117 | }
118 | return [];
119 | },
120 |
121 | /**
122 | * If the parameter may be specified multiple times, adds the given parameter
123 | * to the list of identically-named parameters, unless one already exists with
124 | * the same value. If it may be specified only once, replaces the parameter.
125 | *
126 | * @param {String} name The name of the parameter.
127 | * @param {AjaxSolr.Parameter} [param] The parameter.
128 | * @returns {AjaxSolr.Parameter|Boolean} The parameter, or false.
129 | */
130 | add: function (name, param) {
131 | if (param === undefined) {
132 | param = new AjaxSolr.Parameter({ name: name });
133 | }
134 | if (this.isMultiple(name)) {
135 | if (this.params[name] === undefined) {
136 | this.params[name] = [ param ];
137 | }
138 | else {
139 | if (AjaxSolr.inArray(param.val(), this.values(name)) == -1) {
140 | this.params[name].push(param);
141 | }
142 | else {
143 | return false;
144 | }
145 | }
146 | }
147 | else {
148 | this.params[name] = param;
149 | }
150 | return param;
151 | },
152 |
153 | /**
154 | * Deletes a parameter.
155 | *
156 | * @param {String} name The name of the parameter.
157 | * @param {Number} [index] The index of the parameter.
158 | */
159 | remove: function (name, index) {
160 | if (index === undefined) {
161 | delete this.params[name];
162 | }
163 | else {
164 | this.params[name].splice(index, 1);
165 | if (this.params[name].length == 0) {
166 | delete this.params[name];
167 | }
168 | }
169 | },
170 |
171 | /**
172 | * Finds all parameters with matching values.
173 | *
174 | * @param {String} name The name of the parameter.
175 | * @param {String|Number|String[]|Number[]|RegExp} value The value.
176 | * @returns {String|Number[]} The indices of the parameters found.
177 | */
178 | find: function (name, value) {
179 | if (this.params[name] !== undefined) {
180 | if (this.isMultiple(name)) {
181 | var indices = [];
182 | for (var i = 0, l = this.params[name].length; i < l; i++) {
183 | if (AjaxSolr.equals(this.params[name][i].val(), value)) {
184 | indices.push(i);
185 | }
186 | }
187 | return indices.length ? indices : false;
188 | }
189 | else {
190 | if (AjaxSolr.equals(this.params[name].val(), value)) {
191 | return name;
192 | }
193 | }
194 | }
195 | return false;
196 | },
197 |
198 | /**
199 | * If the parameter may be specified multiple times, creates a parameter using
200 | * the given name and value, and adds it to the list of identically-named
201 | * parameters, unless one already exists with the same value. If it may be
202 | * specified only once, replaces the parameter.
203 | *
204 | * @param {String} name The name of the parameter.
205 | * @param {String|Number|String[]|Number[]} value The value.
206 | * @param {Object} [locals] The parameter's local parameters.
207 | * @returns {AjaxSolr.Parameter|Boolean} The parameter, or false.
208 | */
209 | addByValue: function (name, value, locals) {
210 | if (locals === undefined) {
211 | locals = {};
212 | }
213 | if (this.isMultiple(name) && AjaxSolr.isArray(value)) {
214 | var ret = [];
215 | for (var i = 0, l = value.length; i < l; i++) {
216 | ret.push(this.add(name, new AjaxSolr.Parameter({ name: name, value: value[i], locals: locals })));
217 | }
218 | return ret;
219 | }
220 | else {
221 | return this.add(name, new AjaxSolr.Parameter({ name: name, value: value, locals: locals }));
222 | }
223 | },
224 |
225 | /**
226 | * Deletes any parameter with a matching value.
227 | *
228 | * @param {String} name The name of the parameter.
229 | * @param {String|Number|String[]|Number[]|RegExp} value The value.
230 | * @returns {String|Number[]} The indices deleted.
231 | */
232 | removeByValue: function (name, value) {
233 | var indices = this.find(name, value);
234 | if (indices) {
235 | if (AjaxSolr.isArray(indices)) {
236 | for (var i = indices.length - 1; i >= 0; i--) {
237 | this.remove(name, indices[i]);
238 | }
239 | }
240 | else {
241 | this.remove(indices);
242 | }
243 | }
244 | return indices;
245 | },
246 |
247 | /**
248 | * Returns the Solr parameters as a query string.
249 | *
250 | *
IE6 calls the default toString() if you write store.toString()
251 | * . So, we need to choose another name for toString().
252 | */
253 | string: function () {
254 | var params = [], string;
255 | for (var name in this.params) {
256 | if (this.isMultiple(name)) {
257 | for (var i = 0, l = this.params[name].length; i < l; i++) {
258 | string = this.params[name][i].string();
259 | if (string) {
260 | params.push(string);
261 | }
262 | }
263 | }
264 | else {
265 | string = this.params[name].string();
266 | if (string) {
267 | params.push(string);
268 | }
269 | }
270 | }
271 | return params.join('&');
272 | },
273 |
274 | /**
275 | * Parses a query string into Solr parameters.
276 | *
277 | * @param {String} str The string to parse.
278 | */
279 | parseString: function (str) {
280 | var pairs = str.split('&');
281 | for (var i = 0, l = pairs.length; i < l; i++) {
282 | if (pairs[i]) { // ignore leading, trailing, and consecutive &'s
283 | var param = new AjaxSolr.Parameter();
284 | param.parseString(pairs[i]);
285 | this.add(param.name, param);
286 | }
287 | }
288 | },
289 |
290 | /**
291 | * Returns the exposed parameters as a query string.
292 | *
293 | * @returns {String} A string representation of the exposed parameters.
294 | */
295 | exposedString: function () {
296 | var params = [], string;
297 | for (var i = 0, l = this.exposed.length; i < l; i++) {
298 | if (this.params[this.exposed[i]] !== undefined) {
299 | if (this.isMultiple(this.exposed[i])) {
300 | for (var j = 0, m = this.params[this.exposed[i]].length; j < m; j++) {
301 | string = this.params[this.exposed[i]][j].string();
302 | if (string) {
303 | params.push(string);
304 | }
305 | }
306 | }
307 | else {
308 | string = this.params[this.exposed[i]].string();
309 | if (string) {
310 | params.push(string);
311 | }
312 | }
313 | }
314 | }
315 | return params.join('&');
316 | },
317 |
318 | /**
319 | * Resets the values of the exposed parameters.
320 | */
321 | exposedReset: function () {
322 | for (var i = 0, l = this.exposed.length; i < l; i++) {
323 | this.remove(this.exposed[i]);
324 | }
325 | },
326 |
327 | /**
328 | * Loads the values of exposed parameters from persistent storage. It is
329 | * necessary, in most cases, to reset the values of exposed parameters before
330 | * setting the parameters to the values in storage. This is to ensure that a
331 | * parameter whose name is not present in storage is properly reset.
332 | *
333 | * @param {Boolean} [reset=true] Whether to reset the exposed parameters.
334 | * before loading new values from persistent storage. Default: true.
335 | */
336 | load: function (reset) {
337 | if (reset === undefined) {
338 | reset = true;
339 | }
340 | if (reset) {
341 | this.exposedReset();
342 | }
343 | this.parseString(this.storedString());
344 | },
345 |
346 | /**
347 | * An abstract hook for child implementations.
348 | *
349 | *
Stores the values of the exposed parameters in persistent storage. This
350 | * method should usually be called before each Solr request.
351 | */
352 | save: function () {},
353 |
354 | /**
355 | * An abstract hook for child implementations.
356 | *
357 | *
Returns the string to parse from persistent storage.