1 // $Id$
  2 
  3 /**
  4  * Baseclass for all facet widgets.
  5  *
  6  * @class AbstractFacetWidget
  7  * @augments AjaxSolr.AbstractWidget
  8  */
  9 AjaxSolr.AbstractFacetWidget = AjaxSolr.AbstractWidget.extend(
 10   /** @lends AjaxSolr.AbstractFacetWidget.prototype */
 11   {
 12   /**
 13    * The field to facet on.
 14    *
 15    * @field
 16    * @public
 17    * @type String
 18    */
 19   field: null,
 20 
 21   /**
 22    * Set to <tt>false</tt> to force a single "fq" parameter for this widget.
 23    *
 24    * @field
 25    * @public
 26    * @type Boolean
 27    * @default true
 28    */
 29   multivalue: true,
 30 
 31   init: function () {
 32     this.initStore();
 33   },
 34 
 35   /**
 36    * Add facet parameters to the parameter store.
 37    */
 38   initStore: function () {
 39     /* http://wiki.apache.org/solr/SimpleFacetParameters */
 40     var parameters = [
 41       'facet.prefix',
 42       'facet.sort',
 43       'facet.limit',
 44       'facet.offset',
 45       'facet.mincount',
 46       'facet.missing',
 47       'facet.method',
 48       'facet.enum.cache.minDf'
 49     ];
 50 
 51     this.manager.store.addByValue('facet', true);
 52 
 53     // Set facet.field, facet.date or facet.range to truthy values to add
 54     // related per-field parameters to the parameter store.
 55     if (this['facet.field'] !== undefined) {
 56       this.manager.store.addByValue('facet.field', this.field);
 57     }
 58     else if (this['facet.date'] !== undefined) {
 59       this.manager.store.addByValue('facet.date', this.field);
 60       parameters = parameters.concat([
 61         'facet.date.start',
 62         'facet.date.end',
 63         'facet.date.gap',
 64         'facet.date.hardend',
 65         'facet.date.other',
 66         'facet.date.include'
 67       ]);
 68     }
 69     else if (this['facet.range'] !== undefined) {
 70       this.manager.store.addByValue('facet.range', this.field);
 71       parameters = parameters.concat([
 72         'facet.range.start',
 73         'facet.range.end',
 74         'facet.range.gap',
 75         'facet.range.hardend',
 76         'facet.range.other',
 77         'facet.range.include'
 78       ]);
 79     }
 80 
 81     for (var i = 0, l = parameters.length; i < l; i++) {
 82       if (this[parameters[i]] !== undefined) {
 83         this.manager.store.addByValue('f.' + this.field + '.' + parameters[i], this[parameters[i]]);
 84       }
 85     }
 86   },
 87 
 88   /**
 89    * @returns {Boolean} Whether any filter queries have been set using this
 90    *   widget's facet field.
 91    */
 92   isEmpty: function () {
 93     return !this.manager.store.find('fq', new RegExp('^-?' + this.field + ':'));
 94   },
 95 
 96   /**
 97    * Sets the filter query.
 98    *
 99    * @returns {Boolean} Whether the selection changed.
100    */
101   set: function (value) {
102     return this.changeSelection(function () {
103       var a = this.manager.store.removeByValue('fq', new RegExp('^-?' + this.field + ':')),
104           b = this.manager.store.addByValue('fq', this.fq(value));
105       return a || b;
106     });
107   },
108 
109   /**
110    * Adds a filter query.
111    *
112    * @returns {Boolean} Whether a filter query was added.
113    */
114   add: function (value) {
115     return this.changeSelection(function () {
116       return this.manager.store.addByValue('fq', this.fq(value));
117     });
118   },
119 
120   /**
121    * Removes a filter query.
122    *
123    * @returns {Boolean} Whether a filter query was removed.
124    */
125   remove: function (value) {
126     return this.changeSelection(function () {
127       return this.manager.store.removeByValue('fq', this.fq(value));
128     });
129   },
130 
131   /**
132    * Removes all filter queries using the widget's facet field.
133    *
134    * @returns {Boolean} Whether a filter query was removed.
135    */
136   clear: function () {
137     return this.changeSelection(function () {
138       return this.manager.store.removeByValue('fq', new RegExp('^-?' + this.field + ':'));
139     });
140   },
141 
142   /**
143    * Helper for selection functions.
144    *
145    * @param {Function} Selection function to call.
146    * @returns {Boolean} Whether the selection changed.
147    */
148   changeSelection: function (func) {
149     changed = func.apply(this);
150     if (changed) {
151       this.afterChangeSelection();
152     }
153     return changed;
154   },
155 
156   /**
157    * An abstract hook for child implementations.
158    *
159    * <p>This method is executed after the filter queries change.</p>
160    */
161   afterChangeSelection: function () {},
162 
163   /**
164    * One of "facet.field", "facet.date" or "facet.range" must be set on the
165    * widget in order to determine where the facet counts are stored.
166    *
167    * @returns {Array} An array of objects with the properties <tt>facet</tt> and
168    * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
169    */
170   getFacetCounts: function () {
171     var property;
172     if (this['facet.field'] !== undefined) {
173       property = 'facet_fields';
174     }
175     else if (this['facet.date'] !== undefined) {
176       property = 'facet_dates';
177     }
178     else if (this['facet.range'] !== undefined) {
179       property = 'facet_ranges';
180     }
181     if (property !== undefined) {
182       switch (this.manager.store.get('json.nl').val()) {
183         case 'map':
184           return this.getFacetCountsMap(property);
185         case 'arrarr':
186           return this.getFacetCountsArrarr(property);
187         default:
188           return this.getFacetCountsFlat(property);
189       }
190     }
191     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".';
192   },
193 
194   /**
195    * Used if the facet counts are represented as a JSON object.
196    *
197    * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
198    * @returns {Array} An array of objects with the properties <tt>facet</tt> and
199    * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
200    */
201   getFacetCountsMap: function (property) {
202     var counts = [];
203     for (var facet in this.manager.response.facet_counts[property][this.field]) {
204       counts.push({
205         facet: facet,
206         count: parseInt(this.manager.response.facet_counts[property][this.field][facet])
207       });
208     }
209     return counts;
210   },
211 
212   /**
213    * Used if the facet counts are represented as an array of two-element arrays.
214    *
215    * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
216    * @returns {Array} An array of objects with the properties <tt>facet</tt> and
217    * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
218    */
219   getFacetCountsArrarr: function (property) {
220     var counts = [];
221     for (var i = 0, l = this.manager.response.facet_counts[property][this.field].length; i < l; i++) {
222       counts.push({
223         facet: this.manager.response.facet_counts[property][this.field][i][0],
224         count: parseInt(this.manager.response.facet_counts[property][this.field][i][1])
225       });
226     }
227     return counts;
228   },
229 
230   /**
231    * Used if the facet counts are represented as a flat array.
232    *
233    * @param {String} property "facet_fields", "facet_dates", or "facet_ranges".
234    * @returns {Array} An array of objects with the properties <tt>facet</tt> and
235    * <tt>count</tt>, e.g <tt>{ facet: 'facet', count: 1 }</tt>.
236    */
237   getFacetCountsFlat: function (property) {
238     var counts = [];
239     for (var i = 0, l = this.manager.response.facet_counts[property][this.field].length; i < l; i += 2) {
240       counts.push({
241         facet: this.manager.response.facet_counts[property][this.field][i],
242         count: parseInt(this.manager.response.facet_counts[property][this.field][i+1])
243       });
244     }
245     return counts;
246   },
247 
248   /**
249    * @param {String} value The value.
250    * @returns {Function} Sends a request to Solr if it successfully adds a
251    *   filter query with the given value.
252    */
253   clickHandler: function (value) {
254     var self = this, meth = this.multivalue ? 'add' : 'set';
255     return function () {
256       if (self[meth].call(self, value)) {
257         self.manager.doRequest(0);
258       }
259       return false;
260     }
261   },
262 
263   /**
264    * @param {String} value The value.
265    * @returns {Function} Sends a request to Solr if it successfully removes a
266    *   filter query with the given value.
267    */
268   unclickHandler: function (value) {
269     var self = this;
270     return function () {
271       if (self.remove(value)) {
272         self.manager.doRequest(0);
273       }
274       return false;
275     }
276   },
277 
278   /**
279    * @param {String} value The facet value.
280    * @param {Boolean} exclude Whether to exclude this fq parameter value.
281    * @returns {String} An fq parameter value.
282    */
283   fq: function (value, exclude) {
284     return (exclude ? '-' : '') + this.field + ':' + AjaxSolr.Parameter.escapeValue(value);
285   }
286 });
287