Split files.
Showing
7 changed files
with
1080 additions
and
1054 deletions
... | @@ -11,7 +11,7 @@ module.exports = function(grunt) { | ... | @@ -11,7 +11,7 @@ module.exports = function(grunt) { |
11 | browserOptions: { | 11 | browserOptions: { |
12 | }, | 12 | }, |
13 | model: [ | 13 | model: [ |
14 | 'src/scripts/solr-search/model/**/*.js', | 14 | 'src/scripts/solr/model/**/*.js', |
15 | ], | 15 | ], |
16 | }; | 16 | }; |
17 | /* | 17 | /* | ... | ... |
... | @@ -3,1133 +3,588 @@ define(function(require) { | ... | @@ -3,1133 +3,588 @@ define(function(require) { |
3 | var _ = require('underscore'); | 3 | var _ = require('underscore'); |
4 | var Backbone = require('backbone'); | 4 | var Backbone = require('backbone'); |
5 | var NestedModels = require('backbone-nested-models'); | 5 | var NestedModels = require('backbone-nested-models'); |
6 | //var module = require('module'); | ||
7 | 6 | ||
8 | function getField(obj, key) { | 7 | function getField(obj, key) { |
9 | return obj[key]; | 8 | return obj[key]; |
10 | } | 9 | } |
11 | 10 | ||
12 | function mergeStaticProps(startPtr, endPtr, obj, fieldName) { | ||
13 | var result = obj; | ||
14 | var ptr = startPtr; | ||
15 | while (true) { | ||
16 | result = _.extend(result, _.result(ptr, fieldName)); | ||
17 | if (ptr === endPtr) { | ||
18 | break; | ||
19 | } | ||
20 | ptr = ptr.__super__.constructor; | ||
21 | } | ||
22 | return result; | ||
23 | } | ||
24 | |||
25 | function mergeStaticSets(startPtr, endPtr, obj, fieldName) { | ||
26 | function arrayToMap(array) { | ||
27 | var result = {}; | ||
28 | _.each(array, function(value) { | ||
29 | result[value] = true; | ||
30 | }); | ||
31 | return result; | ||
32 | } | ||
33 | var set = arrayToMap(obj); | ||
34 | var ptr = startPtr; | ||
35 | while (true) { | ||
36 | set = _.extend(set, arrayToMap(_.result(ptr, fieldName))); | ||
37 | if (ptr === endPtr) { | ||
38 | break; | ||
39 | } | ||
40 | ptr = ptr.__super__.constructor; | ||
41 | } | ||
42 | return _.keys(set); | ||
43 | } | ||
44 | |||
45 | function getItemKeyAccessor(item) { | 11 | function getItemKeyAccessor(item) { |
46 | return item.get('key'); | 12 | return item.get('key'); |
47 | } | 13 | } |
48 | var SolrFacets = (function() { | 14 | |
49 | var Facets = NestedModels.mixin(Backbone.Model.extend({ | 15 | var Facets = NestedModels.mixin(Backbone.Model.extend({ |
50 | initialize: function(data, options) { | 16 | initialize: function(data, options) { |
51 | this.url = function() { | 17 | this.url = function() { |
52 | return options.search.url(); | 18 | return options.search.url(); |
53 | }; | 19 | }; |
54 | _.each(this.keys(), _.bind(function(facetName) { | 20 | _.each(this.keys(), _.bind(function(facetName) { |
55 | var facet = this.get(facetName); | 21 | var facet = this.get(facetName); |
56 | _.each({ | 22 | _.each({ |
57 | 'item-change': 'item-change', | 23 | 'item-change': 'item-change', |
58 | 'change:query': 'child-query', | 24 | 'change:query': 'child-query', |
59 | 'change:queryMin': 'item-change', | 25 | 'change:queryMin': 'item-change', |
60 | 'change:queryMax': 'item-change', | 26 | 'change:queryMax': 'item-change', |
61 | }, function(eventValue, eventKey) { | 27 | }, function(eventValue, eventKey) { |
62 | facet.on(eventKey, function(model, value, options) { | 28 | facet.on(eventKey, function(model, value, options) { |
63 | this.trigger(eventValue, facet, null, options); | 29 | this.trigger(eventValue, facet, null, options); |
64 | }, this); | ||
65 | }, this); | 30 | }, this); |
66 | }, this)); | 31 | }, this); |
32 | }, this)); | ||
67 | 33 | ||
68 | this._doSuggestions = _.debounce(_.bind(function(childFacet) { | 34 | this._doSuggestions = _.debounce(_.bind(function(childFacet) { |
69 | if (childFacet.get('query')) { | 35 | if (childFacet.get('query')) { |
70 | this.fetch({ | 36 | this.fetch({ |
71 | data: this.toJSON({childFacet: childFacet, facetSuggestions: true}), | 37 | data: this.toJSON({childFacet: childFacet, facetSuggestions: true}), |
72 | childFacet: childFacet, | 38 | childFacet: childFacet, |
73 | facetSuggestions: true, | 39 | facetSuggestions: true, |
74 | merge: true, | 40 | merge: true, |
75 | traditional: true, | 41 | traditional: true, |
76 | }); | 42 | }); |
77 | } else { | 43 | } else { |
78 | childFacet.set('suggestions', []); | 44 | childFacet.set('suggestions', []); |
79 | } | 45 | } |
80 | }, this), 250); | 46 | }, this), 250); |
81 | this.on('child-query', this._doSuggestions, this); | 47 | this.on('child-query', this._doSuggestions, this); |
82 | 48 | ||
83 | return Facets.__super__.initialize.apply(this, arguments); | 49 | return Facets.__super__.initialize.apply(this, arguments); |
84 | }, | 50 | }, |
85 | resetSearch: function(options) { | 51 | resetSearch: function(options) { |
86 | _.each(this.values(), function(facet) { | 52 | _.each(this.values(), function(facet) { |
87 | facet.get('items').reset(null, options); | 53 | facet.get('items').reset(null, options); |
88 | }); | 54 | }); |
89 | }, | 55 | }, |
90 | applyFacetResults: function(data, options) { | 56 | applyFacetResults: function(data, options) { |
91 | options = options || {}; | 57 | options = options || {}; |
92 | var facetCounts = getField(data, 'facet_counts'); | 58 | var facetCounts = getField(data, 'facet_counts'); |
93 | var facetIntervals = getField(facetCounts, 'facet_intervals'); | 59 | var facetIntervals = getField(facetCounts, 'facet_intervals'); |
94 | var facetRanges = getField(facetCounts, 'facet_ranges'); | 60 | var facetRanges = getField(facetCounts, 'facet_ranges'); |
95 | var facetFields = getField(facetCounts, 'facet_fields'); | 61 | var facetFields = getField(facetCounts, 'facet_fields'); |
96 | var facetQueries = getField(facetCounts, 'facet_queries'); | 62 | var facetQueries = getField(facetCounts, 'facet_queries'); |
97 | var statsFields = getField(data.stats, 'stats_fields'); | 63 | var statsFields = getField(data.stats, 'stats_fields'); |
98 | function getFacetList(facetName, type) { | 64 | function getFacetList(facetName, type) { |
99 | switch (type) { | 65 | switch (type) { |
100 | case 'year': | 66 | case 'year': |
101 | case 'range': | 67 | case 'range': |
102 | return facetRanges[facetName] ? facetRanges[facetName].counts : null; | 68 | return facetRanges[facetName] ? facetRanges[facetName].counts : null; |
103 | case 'interval': | 69 | case 'interval': |
104 | return facetIntervals[facetName] ? facetIntervals[facetName].counts : null; | 70 | return facetIntervals[facetName] ? facetIntervals[facetName].counts : null; |
105 | case 'year-field': | 71 | case 'year-field': |
106 | case 'field': | 72 | case 'field': |
107 | return facetFields[facetName]; | 73 | return facetFields[facetName]; |
108 | } | ||
109 | } | 74 | } |
110 | _.each(this.keys(), _.bind(function(facetName) { | 75 | } |
111 | var facet = this.get(facetName); | 76 | _.each(this.keys(), _.bind(function(facetName) { |
112 | var type = facet.facetType; | 77 | var facet = this.get(facetName); |
78 | var type = facet.facetType; | ||
113 | 79 | ||
114 | var suggestions = []; | 80 | var suggestions = []; |
115 | _.each(getFacetList('suggestions:' + facetName, type), function(value, index) { | 81 | _.each(getFacetList('suggestions:' + facetName, type), function(value, index) { |
116 | if (index % 2 === 0) { | 82 | if (index % 2 === 0) { |
117 | key = value; | 83 | key = value; |
118 | } else if (value > 0) { | 84 | } else if (value > 0) { |
119 | suggestions.push({key: key, value: value}); | 85 | suggestions.push({key: key, value: value}); |
120 | } | ||
121 | }); | ||
122 | facet.set('suggestions', suggestions); | ||
123 | if (!!options.facetSuggestions && options.childFacet === facet) { | ||
124 | return; | ||
125 | } | 86 | } |
87 | }); | ||
88 | facet.set('suggestions', suggestions); | ||
89 | if (!!options.facetSuggestions && options.childFacet === facet) { | ||
90 | return; | ||
91 | } | ||
126 | 92 | ||
127 | var key; | 93 | var key; |
128 | var list = getFacetList(facetName, type); | 94 | var list = getFacetList(facetName, type); |
129 | var newItems = []; | 95 | var newItems = []; |
130 | var items = facet.get('items'); | 96 | var items = facet.get('items'); |
131 | items.invoke('set', 'hidden', true); | 97 | items.invoke('set', 'hidden', true); |
132 | var valueOverrides = {}; | 98 | var valueOverrides = {}; |
133 | var excludeValues = {}; | 99 | var excludeValues = {}; |
134 | function recordIncludeValueIntoExclude(includeValue, key) { | 100 | function recordIncludeValueIntoExclude(includeValue, key) { |
135 | excludeValues[includeValue] = true; | 101 | excludeValues[includeValue] = true; |
136 | } | 102 | } |
137 | switch (type) { | 103 | switch (type) { |
138 | case 'year-field': | 104 | case 'year-field': |
139 | case 'field': | 105 | case 'field': |
140 | _.each(facet.bins, function(includeValues, key) { | 106 | _.each(facet.bins, function(includeValues, key) { |
141 | var tag = facetName + ':' + key; | 107 | var tag = facetName + ':' + key; |
142 | valueOverrides[key] = facetQueries[tag]; | 108 | valueOverrides[key] = facetQueries[tag]; |
143 | _.each(includeValues, recordIncludeValueIntoExclude); | 109 | _.each(includeValues, recordIncludeValueIntoExclude); |
144 | }); | 110 | }); |
145 | break; | 111 | break; |
146 | } | 112 | } |
147 | var addNewItem = _.bind(function addNewItem(key, value) { | 113 | var addNewItem = _.bind(function addNewItem(key, value) { |
148 | if (valueOverrides[key] !== undefined) { | 114 | if (valueOverrides[key] !== undefined) { |
149 | value = valueOverrides[key]; | 115 | value = valueOverrides[key]; |
150 | if (!value) { | 116 | if (!value) { |
151 | return; | ||
152 | } | ||
153 | } else if (excludeValues[key]) { | ||
154 | return; | 117 | return; |
155 | } | 118 | } |
156 | var item = items.get({id: key}); | 119 | } else if (excludeValues[key]) { |
157 | if (item) { | 120 | return; |
158 | item.set({hidden: value === 0, value: value}); | ||
159 | } else { | ||
160 | item = new Item({key: key, value: value}); | ||
161 | item.on('change:checked', function() { | ||
162 | this.trigger('item-change'); | ||
163 | facet.trigger('item-change'); | ||
164 | }, this); | ||
165 | } | ||
166 | newItems.push(item); | ||
167 | }, this); | ||
168 | |||
169 | function checkOther(set, key) { | ||
170 | if (set.indexOf(facet.other) !== -1) { | ||
171 | addNewItem(key, facetRanges[facetName][key]); | ||
172 | } | ||
173 | } | ||
174 | checkOther(['all', 'before'], 'before'); | ||
175 | |||
176 | _.each(list, function(value, index) { | ||
177 | if (index % 2 === 0) { | ||
178 | key = value; | ||
179 | } else if (value > 0) { | ||
180 | addNewItem(key, value); | ||
181 | } | ||
182 | }); | ||
183 | switch (type) { | ||
184 | case 'year-field': | ||
185 | case 'field': | ||
186 | _.each(facet.bins, function(includeValues, key) { | ||
187 | var tag = facetName + ':' + key; | ||
188 | addNewItem(key, facetQueries[tag]); | ||
189 | }); | ||
190 | break; | ||
191 | } | ||
192 | items.set(newItems, {remove: false}); | ||
193 | if (facet.facetStats) { | ||
194 | var thisFacetStats = statsFields[facetName]; | ||
195 | facet.set({minValue: thisFacetStats.min, maxValue: thisFacetStats.max}); | ||
196 | } | ||
197 | }, this)); | ||
198 | }, | ||
199 | getFacetFormData: function(options) { | ||
200 | options = options || {}; | ||
201 | var result = { | ||
202 | facet: true, | ||
203 | stats: true, | ||
204 | 'facet.missing': true, | ||
205 | 'facet.field': [], | ||
206 | 'facet.range': [], | ||
207 | 'facet.interval': [], | ||
208 | 'facet.query': [], | ||
209 | 'stats.field': [], | ||
210 | fq: [], | ||
211 | }; | ||
212 | _.each(this.keys(), _.bind(function(facetName) { | ||
213 | var facet = this.get(facetName); | ||
214 | var queryField = facet.queryField; | ||
215 | var facetField = facet.facetField ? facet.facetField : queryField; | ||
216 | var type = facet.facetType; | ||
217 | var valueFormatter; | ||
218 | var facetOptions = {}; | ||
219 | var quoteFormatter = function(value) { | ||
220 | return value.replace ? ('"' + value.replace(/"/, '\\"') + '"') : value; | ||
221 | }; | ||
222 | var rangeFormatter = function(from, to) { | ||
223 | return '[' + quoteFormatter(from) + ' TO ' + quoteFormatter(to) + ']'; | ||
224 | }; | ||
225 | switch (type) { | ||
226 | case 'year': | ||
227 | type = 'range'; | ||
228 | quoteFormatter = function(value) { | ||
229 | return value.toISOString(); | ||
230 | }; | ||
231 | valueFormatter = function(item) { | ||
232 | var key = item.get('key'); | ||
233 | var fromDate, toDate; | ||
234 | if (key === 'before') { | ||
235 | return rangeFormatter('*', facet.rangeStart + '-1SECOND'); | ||
236 | } | ||
237 | fromDate = new Date(key); | ||
238 | toDate = new Date(key); | ||
239 | toDate.setUTCFullYear(toDate.getUTCFullYear() + 1); | ||
240 | return rangeFormatter(fromDate, toDate); | ||
241 | }; | ||
242 | if (facet.other) { | ||
243 | facetOptions['facet.range.other'] = facet.other; | ||
244 | } | ||
245 | break; | ||
246 | case 'range': | ||
247 | case 'interval': | ||
248 | valueFormatter = function(item) { | ||
249 | var key = parseInt(item.get('key')); | ||
250 | return rangeFormatter(key, key + facet.rangeGap); | ||
251 | }; | ||
252 | break; | ||
253 | case 'year-field': | ||
254 | type = 'field'; | ||
255 | quoteFormatter = function(value) { | ||
256 | return new Date(value).toISOString(); | ||
257 | }; | ||
258 | valueFormatter = facet.queryValue ? facet.queryValue : function(value) { | ||
259 | return quoteFormatter(getItemKeyAccessor(value)); | ||
260 | }; | ||
261 | _.each(facet.bins, function(includeValues, key) { | ||
262 | var query = _.map(includeValues, function(includeValue, index) { | ||
263 | return queryField + ':\'' + includeValue + '\''; | ||
264 | }).join(' OR '); | ||
265 | result['facet.query'].push('{!key=' + facetName + ':' + key + ' tag=' + facetName + ':' + key + '}(' + query + ')'); | ||
266 | }); | ||
267 | break; | ||
268 | case 'field': | ||
269 | valueFormatter = facet.queryValue ? facet.queryValue : function(value) { | ||
270 | return quoteFormatter(getItemKeyAccessor(value)); | ||
271 | }; | ||
272 | _.each(facet.bins, function(includeValues, key) { | ||
273 | var query = _.map(includeValues, function(includeValue, index) { | ||
274 | return queryField + ':' + includeValue; | ||
275 | }).join(' OR '); | ||
276 | result['facet.query'].push('{!key=' + facetName + ':' + key + ' tag=' + facetName + ':' + key + '}(' + query + ')'); | ||
277 | }); | ||
278 | break; | ||
279 | } | ||
280 | switch (type) { | ||
281 | case 'range': | ||
282 | facetOptions['facet.range.start'] = facet.rangeStart; | ||
283 | facetOptions['facet.range.end'] = facet.rangeEnd; | ||
284 | facetOptions['facet.range.gap'] = facet.rangeGap; | ||
285 | break; | ||
286 | case 'interval': | ||
287 | facetOptions['facet.interval.set'] = '[*,*]'; | ||
288 | break; | ||
289 | } | ||
290 | var facetValues = []; | ||
291 | var queryMax = facet.get('queryMax'); | ||
292 | var queryMin = facet.get('queryMin'); | ||
293 | if (queryMax !== undefined && queryMin !== undefined) { | ||
294 | facetValues.push(rangeFormatter(queryMin, queryMax)); | ||
295 | } | 121 | } |
296 | facet.get('items').each(function(item, index) { | 122 | var item = items.get({id: key}); |
297 | if (!item.get('hidden') && item.get('checked')) { | 123 | if (item) { |
298 | facetValues.push(valueFormatter(item)); | 124 | item.set({hidden: value === 0, value: value}); |
299 | } | ||
300 | }); | ||
301 | facetOptions.key = facetName; | ||
302 | if (facetValues.length) { | ||
303 | facetOptions.ex = facetName; | ||
304 | result.fq.push('{!tag=' + facetName + '}' + facetField + ':(' + facetValues.join(' OR ') + ')'); | ||
305 | } else { | 125 | } else { |
306 | facetOptions.tag = facetName; | 126 | item = new Item({key: key, value: value}); |
307 | } | 127 | item.on('change:checked', function() { |
308 | if (!!options.facetSuggestions) { | 128 | this.trigger('item-change'); |
309 | var childFacet = options.childFacet; | 129 | facet.trigger('item-change'); |
310 | if (childFacet === facet) { | 130 | }, this); |
311 | facetOptions['facet.contains'] = facet.get('query'); | ||
312 | facetOptions['facet.contains.ignoreCase'] = true; | ||
313 | facetOptions['facet.mincount'] = 1; | ||
314 | } | ||
315 | } | ||
316 | var facetOptionList = []; | ||
317 | _.each(facetOptions, function(value, key) { | ||
318 | if (value !== undefined) { | ||
319 | facetOptionList.push(key + '=' + value); | ||
320 | } | ||
321 | }); | ||
322 | result['facet.' + type].push('{!' + facetOptionList.join(' ') + '}' + queryField); | ||
323 | if (facet.facetStats) { | ||
324 | result['stats.field'].push('{!' + facetOptionList.join(' ') + '}' + queryField); | ||
325 | } | ||
326 | var facetQuery = facet.get('query'); | ||
327 | if (facetQuery !== null && facetQuery !== '') { | ||
328 | facetOptions.key = 'suggestions:' + facetName; | ||
329 | facetOptionList = []; | ||
330 | _.each(facetOptions, function(value, key) { | ||
331 | if (value !== undefined) { | ||
332 | facetOptionList.push(key + '=' + value); | ||
333 | } | ||
334 | }); | ||
335 | result['facet.' + type].push('{!' + facetOptionList.join(' ') + '}' + queryField); | ||
336 | } | 131 | } |
337 | }, this)); | 132 | newItems.push(item); |
338 | return result; | 133 | }, this); |
339 | }, | ||
340 | |||
341 | |||
342 | 134 | ||
343 | toJSON: function toJSON(options) { | 135 | function checkOther(set, key) { |
344 | if (!!options.facetSuggestions) { | 136 | if (set.indexOf(facet.other) !== -1) { |
345 | return _.extend({ | 137 | addNewItem(key, facetRanges[facetName][key]); |
346 | rows: 0, | ||
347 | facet: true, | ||
348 | wt: 'json', | ||
349 | q: '*:*', | ||
350 | }, this.getFacetFormData(options)); | ||
351 | } | ||
352 | }, | ||
353 | parse: function parse(data, options) { | ||
354 | if (!!options.facetSuggestions) { | ||
355 | this.applyFacetResults(data, options); | ||
356 | } | ||
357 | return null; | ||
358 | }, | ||
359 | })); | ||
360 | var Sort = Facets.Sort = { | ||
361 | standard: function standardSort(a, b) { | ||
362 | if (a < b) { | ||
363 | return -1; | ||
364 | } else if (a > b) { | ||
365 | return 1; | ||
366 | } else { | ||
367 | return 0; | ||
368 | } | ||
369 | }, | ||
370 | date: function dateSort(a, b) { | ||
371 | return Sort.standard.call(this, a, b); | ||
372 | }, | ||
373 | number: function numberSort(a, b) { | ||
374 | return Sort.standard.call(this, parseInt(a), parseInt(b)); | ||
375 | }, | ||
376 | }; | ||
377 | function wrapComparatorForAllBeforeAfter(comparator, doBefore, doAfter) { | ||
378 | if (!doBefore && !doAfter) { | ||
379 | return comparator; | ||
380 | } | ||
381 | return function beforeAfterComparator(a, b) { | ||
382 | if (doBefore) { | ||
383 | if (a === 'before') { | ||
384 | return -1; | ||
385 | } else if (b === 'before') { | ||
386 | return 1; | ||
387 | } | 138 | } |
388 | } | 139 | } |
389 | if (doAfter) { | 140 | checkOther(['all', 'before'], 'before'); |
390 | if (a === 'after') { | 141 | |
391 | return 1; | 142 | _.each(list, function(value, index) { |
392 | } else if (b === 'after') { | 143 | if (index % 2 === 0) { |
393 | return -1; | 144 | key = value; |
145 | } else if (value > 0) { | ||
146 | addNewItem(key, value); | ||
394 | } | 147 | } |
148 | }); | ||
149 | switch (type) { | ||
150 | case 'year-field': | ||
151 | case 'field': | ||
152 | _.each(facet.bins, function(includeValues, key) { | ||
153 | var tag = facetName + ':' + key; | ||
154 | addNewItem(key, facetQueries[tag]); | ||
155 | }); | ||
156 | break; | ||
157 | } | ||
158 | items.set(newItems, {remove: false}); | ||
159 | if (facet.facetStats) { | ||
160 | var thisFacetStats = statsFields[facetName]; | ||
161 | facet.set({minValue: thisFacetStats.min, maxValue: thisFacetStats.max}); | ||
395 | } | 162 | } |
396 | return comparator.call(this, a, b); | 163 | }, this)); |
164 | }, | ||
165 | getFacetFormData: function(options) { | ||
166 | options = options || {}; | ||
167 | var result = { | ||
168 | facet: true, | ||
169 | stats: true, | ||
170 | 'facet.missing': true, | ||
171 | 'facet.field': [], | ||
172 | 'facet.range': [], | ||
173 | 'facet.interval': [], | ||
174 | 'facet.query': [], | ||
175 | 'stats.field': [], | ||
176 | fq: [], | ||
397 | }; | 177 | }; |
398 | } | 178 | _.each(this.keys(), _.bind(function(facetName) { |
399 | var Facet = Facets.Facet = Backbone.Model.extend({ | 179 | var facet = this.get(facetName); |
400 | defaults: function defaults() { | 180 | var queryField = facet.queryField; |
401 | return { | 181 | var facetField = facet.facetField ? facet.facetField : queryField; |
402 | formName: null, | 182 | var type = facet.facetType; |
403 | query: null, | 183 | var valueFormatter; |
404 | suggestions: [], | 184 | var facetOptions = {}; |
185 | var quoteFormatter = function(value) { | ||
186 | return value.replace ? ('"' + value.replace(/"/, '\\"') + '"') : value; | ||
405 | }; | 187 | }; |
406 | }, | 188 | var rangeFormatter = function(from, to) { |
407 | initialize: function(data, options) { | 189 | return '[' + quoteFormatter(from) + ' TO ' + quoteFormatter(to) + ']'; |
408 | this.set('all', true); | 190 | }; |
409 | this.on('change:queryMax change:queryMin', function() { | 191 | switch (type) { |
410 | this.set('hasQuery', this.get('queryMin') !== undefined && this.get('queryMax') !== undefined); | 192 | case 'year': |
411 | }, this); | 193 | type = 'range'; |
412 | (function(self) { | 194 | quoteFormatter = function(value) { |
413 | var skipCallback; | 195 | return value.toISOString(); |
414 | self.on('item-change change:queryMax change:queryMin', function() { | 196 | }; |
415 | switch (skipCallback) { | 197 | valueFormatter = function(item) { |
416 | case 'change:checkedKeys': | 198 | var key = item.get('key'); |
417 | case 'change:all': | 199 | var fromDate, toDate; |
418 | return; | 200 | if (key === 'before') { |
419 | } | 201 | return rangeFormatter('*', facet.rangeStart + '-1SECOND'); |
420 | var checkedKeys = []; | ||
421 | this.get('items').each(function(facetItem) { | ||
422 | if (facetItem.get('checked')) { | ||
423 | checkedKeys.push(facetItem.get('key')); | ||
424 | } | 202 | } |
425 | }); | 203 | fromDate = new Date(key); |
426 | var checkedCount = checkedKeys.length; | 204 | toDate = new Date(key); |
427 | if (this.get('queryMin') !== undefined && this.get('queryMax') !== undefined) { | 205 | toDate.setUTCFullYear(toDate.getUTCFullYear() + 1); |
428 | checkedCount++; | 206 | return rangeFormatter(fromDate, toDate); |
429 | } | 207 | }; |
430 | skipCallback = 'item-change'; | 208 | if (facet.other) { |
431 | try { | 209 | facetOptions['facet.range.other'] = facet.other; |
432 | this.set({ | ||
433 | all: checkedCount === 0, | ||
434 | checkedKeys: checkedKeys, | ||
435 | |||
436 | }); | ||
437 | } finally { | ||
438 | skipCallback = null; | ||
439 | } | ||
440 | }, self); | ||
441 | self.on('change:all', function() { | ||
442 | switch (skipCallback) { | ||
443 | case 'change:checkedKeys': | ||
444 | case 'item-change': | ||
445 | return; | ||
446 | } | ||
447 | skipCallback = 'change:all'; | ||
448 | try { | ||
449 | this.get('items').invoke('set', {checked: false}); | ||
450 | this.set({ | ||
451 | checkedKeys: [], | ||
452 | queryMax: undefined, | ||
453 | queryMin: undefined, | ||
454 | }); | ||
455 | } finally { | ||
456 | skipCallback = null; | ||
457 | } | ||
458 | }, self); | ||
459 | self.on('change:checkedKeys', function() { | ||
460 | switch (skipCallback) { | ||
461 | case 'item-change': | ||
462 | case 'change:all': | ||
463 | return; | ||
464 | } | ||
465 | var checkedKeys = {}; | ||
466 | _.each(this.get('checkedKeys'), function(value, i) { | ||
467 | checkedKeys[value] = true; | ||
468 | }); | ||
469 | skipCallback = 'change:checkedKeys'; | ||
470 | var checkedCount = 0; | ||
471 | if (this.get('queryMin') !== undefined && this.get('queryMax') !== undefined) { | ||
472 | checkedCount++; | ||
473 | } | ||
474 | try { | ||
475 | this.get('items').each(function(facetItem) { | ||
476 | var newChecked = checkedKeys[facetItem.get('key')]; | ||
477 | var currentChecked = facetItem.get('checked'); | ||
478 | if (newChecked !== currentChecked) { | ||
479 | facetItem.set('checked', newChecked); | ||
480 | } | ||
481 | if (facetItem.get('checked')) { | ||
482 | checkedCount++; | ||
483 | } | ||
484 | }); | ||
485 | this.set({ | ||
486 | all: checkedCount === 0, | ||
487 | }); | ||
488 | } finally { | ||
489 | skipCallback = null; | ||
490 | } | 210 | } |
491 | }, self); | 211 | break; |
492 | })(this); | ||
493 | var sortKeyExtractor = options.sortKeyExtractor; | ||
494 | if (!sortKeyExtractor) { | ||
495 | sortKeyExtractor = getItemKeyAccessor; | ||
496 | } | ||
497 | var comparator = options.comparator; | ||
498 | if (!comparator) { | ||
499 | comparator = Sort.standard; | ||
500 | } else if (typeof comparator === 'string') { | ||
501 | comparator = Sort[comparator]; | ||
502 | } | ||
503 | switch (options.facetType) { | ||
504 | case 'year': | ||
505 | case 'range': | 212 | case 'range': |
506 | case 'interval': | 213 | case 'interval': |
507 | comparator = wrapComparatorForAllBeforeAfter(comparator, ['all', 'before'].indexOf(options.other) !== -1, ['all', 'after'].indexOf(options.other) !== -1); | 214 | valueFormatter = function(item) { |
215 | var key = parseInt(item.get('key')); | ||
216 | return rangeFormatter(key, key + facet.rangeGap); | ||
217 | }; | ||
508 | break; | 218 | break; |
509 | } | 219 | case 'year-field': |
510 | var formatter = options.formatter; | 220 | type = 'field'; |
511 | if (!formatter) { | 221 | quoteFormatter = function(value) { |
512 | var labelMap = options.labelMap; | 222 | return new Date(value).toISOString(); |
513 | if (labelMap) { | ||
514 | formatter = function(item) { | ||
515 | var value = item.get('key'); | ||
516 | var label = options.labelMap[value.toUpperCase()]; | ||
517 | return label ? label : value; | ||
518 | }; | 223 | }; |
519 | } else { | 224 | valueFormatter = facet.queryValue ? facet.queryValue : function(value) { |
520 | formatter = getItemKeyAccessor; | 225 | return quoteFormatter(getItemKeyAccessor(value)); |
521 | } | 226 | }; |
522 | } | 227 | _.each(facet.bins, function(includeValues, key) { |
523 | this.formatter = formatter; | 228 | var query = _.map(includeValues, function(includeValue, index) { |
524 | comparator = (function(comparator) { | 229 | return queryField + ':\'' + includeValue + '\''; |
525 | return function facetItemValue(a, b) { | 230 | }).join(' OR '); |
526 | var keyA = sortKeyExtractor(a); | 231 | result['facet.query'].push('{!key=' + facetName + ':' + key + ' tag=' + facetName + ':' + key + '}(' + query + ')'); |
527 | var keyB = sortKeyExtractor(b); | 232 | }); |
528 | switch (this.get('orderByDirection')) { | 233 | break; |
529 | case 'desc': | 234 | case 'field': |
530 | var tmp = keyA; | 235 | valueFormatter = facet.queryValue ? facet.queryValue : function(value) { |
531 | keyA = keyB; | 236 | return quoteFormatter(getItemKeyAccessor(value)); |
532 | keyB = tmp; | 237 | }; |
533 | break; | 238 | _.each(facet.bins, function(includeValues, key) { |
534 | } | 239 | var query = _.map(includeValues, function(includeValue, index) { |
535 | return comparator.call(this, sortKeyExtractor(a), sortKeyExtractor(b)); | 240 | return queryField + ':' + includeValue; |
536 | }; | 241 | }).join(' OR '); |
537 | })(comparator); | 242 | result['facet.query'].push('{!key=' + facetName + ':' + key + ' tag=' + facetName + ':' + key + '}(' + query + ')'); |
538 | this.set('items', new ItemCollection([], {comparator: comparator})); | 243 | }); |
539 | this.facetType = options.facetType; | ||
540 | this.facetStats = options.facetStats; | ||
541 | this.other = options.other; | ||
542 | this.facetField = options.facetField; | ||
543 | this.queryField = options.queryField; | ||
544 | this.queryValue = options.queryValue; | ||
545 | this.bins = options.bins; | ||
546 | switch (this.facetType) { | ||
547 | case 'year': | ||
548 | this.rangeStart = options.start; | ||
549 | this.rangeEnd = options.end; | ||
550 | this.rangeGap = '+' + (options.gap ? options.gap : 1) + 'YEAR'; | ||
551 | break; | 244 | break; |
245 | } | ||
246 | switch (type) { | ||
552 | case 'range': | 247 | case 'range': |
553 | this.rangeStart = options.start; | 248 | facetOptions['facet.range.start'] = facet.rangeStart; |
554 | this.rangeEnd = options.end; | 249 | facetOptions['facet.range.end'] = facet.rangeEnd; |
555 | this.rangeGap = options.gap; | 250 | facetOptions['facet.range.gap'] = facet.rangeGap; |
556 | break; | 251 | break; |
557 | case 'field': | ||
558 | case 'year-field': | ||
559 | case 'interval': | 252 | case 'interval': |
253 | facetOptions['facet.interval.set'] = '[*,*]'; | ||
560 | break; | 254 | break; |
561 | default: | ||
562 | var e = new Error('Unsupported facet type: ' + this.facetType); | ||
563 | e.facet = this; | ||
564 | throw e; | ||
565 | } | 255 | } |
566 | return Facet.__super__.initialize.apply(this, arguments); | 256 | var facetValues = []; |
567 | }, | 257 | var queryMax = facet.get('queryMax'); |
568 | /* | 258 | var queryMin = facet.get('queryMin'); |
569 | toJSON: function toJSON(options) { | 259 | if (queryMax !== undefined && queryMin !== undefined) { |
570 | if (!!options.facetSuggestions) { | 260 | facetValues.push(rangeFormatter(queryMin, queryMax)); |
571 | return { | 261 | } |
572 | rows: 0, | 262 | facet.get('items').each(function(item, index) { |
573 | facet: true, | 263 | if (!item.get('hidden') && item.get('checked')) { |
574 | wt: 'json', | 264 | facetValues.push(valueFormatter(item)); |
575 | q: '*:*', | 265 | } |
576 | 'facet.contains': this.get('query'), | 266 | }); |
577 | 'facet.contains.ignoreCase': true, | 267 | facetOptions.key = facetName; |
578 | 'facet.field': this.queryField, | 268 | if (facetValues.length) { |
579 | 'facet.mincount': 1, | 269 | facetOptions.ex = facetName; |
580 | }; | 270 | result.fq.push('{!tag=' + facetName + '}' + facetField + ':(' + facetValues.join(' OR ') + ')'); |
271 | } else { | ||
272 | facetOptions.tag = facetName; | ||
581 | } | 273 | } |
582 | }, | ||
583 | parse: function parse(data, options) { | ||
584 | if (!!options.facetSuggestions) { | 274 | if (!!options.facetSuggestions) { |
585 | var suggestions = []; | 275 | var childFacet = options.childFacet; |
586 | var key; | 276 | if (childFacet === facet) { |
587 | _.each(data.facet_counts.facet_fields[this.queryField], function(value, index) { | 277 | facetOptions['facet.contains'] = facet.get('query'); |
588 | if (index % 2 === 0) { | 278 | facetOptions['facet.contains.ignoreCase'] = true; |
589 | key = value; | 279 | facetOptions['facet.mincount'] = 1; |
590 | } else if (value > 0) { | 280 | } |
591 | suggestions.push({key: key, value: value}); | 281 | } |
282 | var facetOptionList = []; | ||
283 | _.each(facetOptions, function(value, key) { | ||
284 | if (value !== undefined) { | ||
285 | facetOptionList.push(key + '=' + value); | ||
286 | } | ||
287 | }); | ||
288 | result['facet.' + type].push('{!' + facetOptionList.join(' ') + '}' + queryField); | ||
289 | if (facet.facetStats) { | ||
290 | result['stats.field'].push('{!' + facetOptionList.join(' ') + '}' + queryField); | ||
291 | } | ||
292 | var facetQuery = facet.get('query'); | ||
293 | if (facetQuery !== null && facetQuery !== '') { | ||
294 | facetOptions.key = 'suggestions:' + facetName; | ||
295 | facetOptionList = []; | ||
296 | _.each(facetOptions, function(value, key) { | ||
297 | if (value !== undefined) { | ||
298 | facetOptionList.push(key + '=' + value); | ||
592 | } | 299 | } |
593 | }); | 300 | }); |
594 | return { | 301 | result['facet.' + type].push('{!' + facetOptionList.join(' ') + '}' + queryField); |
595 | suggestions: suggestions, | ||
596 | }; | ||
597 | } | 302 | } |
598 | return null; | 303 | }, this)); |
599 | }, | 304 | return result; |
600 | */ | 305 | }, |
601 | }); | ||
602 | var ItemCollection = Backbone.Collection.extend({ | ||
603 | remove: function() { | ||
604 | } | ||
605 | }); | ||
606 | var Item = Facet.Item = Backbone.Model.extend({ | ||
607 | idAttribute: 'key', | ||
608 | defaults: { | ||
609 | checked: false, | ||
610 | hidden: false, | ||
611 | }, | ||
612 | _initialize: function(data, options) { | ||
613 | this.on('add', function(model, parent, options) { | ||
614 | // added to a collection | ||
615 | this.on('change:checked', function() { | ||
616 | parent.trigger('item-change'); | ||
617 | }); | ||
618 | }, this); | ||
619 | return Item.__super__.initialize.apply(this, arguments); | ||
620 | } | ||
621 | }); | ||
622 | 306 | ||
623 | return Facets; | ||
624 | })(); | ||
625 | var Pagination = (function() { | ||
626 | function stepFalse(e) { | ||
627 | e.preventDefault(); | ||
628 | return false; | ||
629 | } | ||
630 | var Pagination = Backbone.Model.extend({ | ||
631 | defaults: { | ||
632 | currentPage: 1, | ||
633 | hasNext: false, | ||
634 | hasNextJumpPage: false, | ||
635 | hasPrevious: false, | ||
636 | hasPreviousJumpPage: false, | ||
637 | nextJumpPage: undefined, | ||
638 | nextPage: undefined, | ||
639 | pages: [], | ||
640 | pageJump: 5, | ||
641 | pageSize: 10, | ||
642 | previousJumpPage: undefined, | ||
643 | previousPage: undefined, | ||
644 | totalCount: 0, | ||
645 | totalPages: 0, | ||
646 | }, | ||
647 | initialize: function(data, options) { | ||
648 | var buildPages = function buildPages() { | ||
649 | var currentPage = parseInt(this.get('currentPage')); | ||
650 | var pageSize = parseInt(this.get('pageSize')); | ||
651 | var totalCount = parseInt(this.get('totalCount')); | ||
652 | if (!currentPage || !pageSize || !totalCount) { | ||
653 | return; | ||
654 | } | ||
655 | var pages = []; | ||
656 | var totalPages = Math.floor((totalCount + pageSize - 1) / pageSize); | ||
657 | function addPage(self, i) { | ||
658 | pages.push({ | ||
659 | current: i === currentPage, | ||
660 | jump: function() { | ||
661 | self.set('currentPage', i); | ||
662 | return true; | ||
663 | }, | ||
664 | number: i, | ||
665 | }); | ||
666 | } | ||
667 | var startAt = currentPage - 4, endAt = currentPage + 5; | ||
668 | if (startAt < 1) { | ||
669 | endAt += (1 - startAt); | ||
670 | } | ||
671 | if (endAt > totalPages) { | ||
672 | startAt -= endAt - totalPages - 1; | ||
673 | endAt = totalPages + 1; | ||
674 | } | ||
675 | if (startAt < 1) { | ||
676 | startAt = 1; | ||
677 | } | ||
678 | if (endAt - startAt < 9) { | ||
679 | /* global console:false */ | ||
680 | console.log('foo'); | ||
681 | } | ||
682 | 307 | ||
683 | for (var i = startAt; i < endAt; i++) { | 308 | |
684 | if (i > 0 && i <= totalPages) { | 309 | toJSON: function toJSON(options) { |
685 | addPage(this, i); | 310 | if (!!options.facetSuggestions) { |
686 | } | 311 | return _.extend({ |
687 | } | 312 | rows: 0, |
688 | var hasPrevious = currentPage > 1; | 313 | facet: true, |
689 | var hasNext = currentPage < totalPages; | 314 | wt: 'json', |
690 | var pageJump = this.get('pageJump'); | 315 | q: '*:*', |
691 | var nextJumpPage, previousJumpPage, hasNextJump, hasPreviousJump; | 316 | }, this.getFacetFormData(options)); |
692 | if (pageJump) { | ||
693 | nextJumpPage = currentPage + pageJump; | ||
694 | previousJumpPage = currentPage - pageJump; | ||
695 | hasNextJump = nextJumpPage < totalPages; | ||
696 | hasPreviousJump = previousJumpPage > 0; | ||
697 | } else { | ||
698 | hasNextJump = false; | ||
699 | hasPreviousJump = false; | ||
700 | } | ||
701 | this.set({ | ||
702 | hasNext: hasNext, | ||
703 | hasNextJump: hasNextJump, | ||
704 | hasPrevious: hasPrevious, | ||
705 | hasPreviousJump: hasPreviousJump, | ||
706 | nextJumpPage: hasNextJump ? nextJumpPage : undefined, | ||
707 | nextPage: hasNext ? currentPage + 1 : undefined, | ||
708 | pages: pages, | ||
709 | previousJumpPage: hasNextJump ? previousJumpPage : undefined, | ||
710 | previousPage: hasPrevious ? currentPage - 1 : undefined, | ||
711 | totalPages: totalPages, | ||
712 | }); | ||
713 | }; | ||
714 | this.on('change:pageSize change:currentPage change:totalCount', buildPages, this); | ||
715 | var installActions = _.bind(function installActions(eventName, nextName, hasNextName, previousName, hasPreviousName, pageCount) { | ||
716 | this.on(eventName, function() { | ||
717 | var hasNext = this.get(hasNextName); | ||
718 | var hasPrevious = this.get(hasPreviousName); | ||
719 | var next, previous; | ||
720 | if (hasNext) { | ||
721 | next = _.bind(function(e) { | ||
722 | e.preventDefault(); | ||
723 | this.set('currentPage', this.get('currentPage') + pageCount); | ||
724 | return false; | ||
725 | }, this); | ||
726 | } else { | ||
727 | next = stepFalse; | ||
728 | } | ||
729 | if (hasPrevious) { | ||
730 | previous = _.bind(function(e) { | ||
731 | e.preventDefault(); | ||
732 | this.set('currentPage', this.get('currentPage') - pageCount); | ||
733 | return false; | ||
734 | }, this); | ||
735 | } else { | ||
736 | previous = stepFalse; | ||
737 | } | ||
738 | this.set(nextName, next); | ||
739 | this.set(previousName, previous); | ||
740 | }, this); | ||
741 | this[nextName] = _.bind(function() { | ||
742 | return this.get(nextName)(); | ||
743 | }, this); | ||
744 | this[previousName] = _.bind(function() { | ||
745 | return this.get(previousName)(); | ||
746 | }, this); | ||
747 | }, this); | ||
748 | this.on('change:pageJump', function() { | ||
749 | var pageJump = this.get('pageJump'); | ||
750 | installActions('change:hasNextJump change:hasPreviousJump', 'nextJump', 'hasNextJump', 'previousJump', 'hasPreviousJump', pageJump); | ||
751 | }, this); | ||
752 | installActions('change:hasNext change:hasPrevious', 'next', 'hasNext', 'previous', 'hasPrevious', 1); | ||
753 | buildPages.apply(this); | ||
754 | this.trigger('change:pageJump'); | ||
755 | return Pagination.__super__.initialize.apply(this, arguments); | ||
756 | } | 317 | } |
757 | }); | ||
758 | return Pagination; | ||
759 | })(); | ||
760 | var Ordering = Backbone.Model.extend({ | ||
761 | defaults: { | ||
762 | value: null, | ||
763 | items: new Backbone.Collection(), | ||
764 | }, | 318 | }, |
765 | initialize: function(data, options) { | 319 | parse: function parse(data, options) { |
766 | if (this.get('value') === null) { | 320 | if (!!options.facetSuggestions) { |
767 | var firstItem = this.get('items').at(0); | 321 | this.applyFacetResults(data, options); |
768 | if (firstItem) { | ||
769 | this.set('value', firstItem.get('value')); | ||
770 | } | ||
771 | } | 322 | } |
323 | return null; | ||
772 | }, | 324 | }, |
773 | parse: function(data) { | 325 | })); |
774 | var result = _.clone(data); | 326 | var Sort = Facets.Sort = { |
775 | if (result.items && !(result.items instanceof Backbone.Collection)) { | 327 | standard: function standardSort(a, b) { |
776 | result.items = new Backbone.Collection(result.items, {parse: true}); | 328 | if (a < b) { |
329 | return -1; | ||
330 | } else if (a > b) { | ||
331 | return 1; | ||
332 | } else { | ||
333 | return 0; | ||
777 | } | 334 | } |
778 | return result; | ||
779 | }, | 335 | }, |
780 | }); | 336 | date: function dateSort(a, b) { |
781 | var QueryTextField = Backbone.Model.extend({ | 337 | return Sort.standard.call(this, a, b); |
782 | defaults: { | ||
783 | formName: null, | ||
784 | name: null, | ||
785 | queries: [], | ||
786 | fields: null, | ||
787 | multi: false | ||
788 | }, | 338 | }, |
789 | }); | 339 | number: function numberSort(a, b) { |
790 | var SolrSearch = Pagination.extend({ | 340 | return Sort.standard.call(this, parseInt(a), parseInt(b)); |
791 | url: function url() { | ||
792 | return this.constructor.selectUrl; | ||
793 | }, | 341 | }, |
794 | defaults: function defaults() { | 342 | }; |
795 | var constructor = this.constructor; | 343 | function wrapComparatorForAllBeforeAfter(comparator, doBefore, doAfter) { |
796 | var formNameMap = {}; | 344 | if (!doBefore && !doAfter) { |
797 | var facets = new SolrFacets(this.constructor.facets, {search: this}); | 345 | return comparator; |
798 | _.each(facets.values(), function(facet) { | 346 | } |
799 | var formName = facet.get('formName'); | 347 | return function beforeAfterComparator(a, b) { |
800 | if (formName) { | 348 | if (doBefore) { |
801 | formNameMap[formName] = facet; | 349 | if (a === 'before') { |
350 | return -1; | ||
351 | } else if (b === 'before') { | ||
352 | return 1; | ||
802 | } | 353 | } |
803 | }); | 354 | } |
804 | var queryFields = new Backbone.Model(); | 355 | if (doAfter) { |
805 | _.each(constructor.queryTextFields, function(definition, queryName) { | 356 | if (a === 'after') { |
806 | var qtf = new QueryTextField({formName: definition.formName, name: queryName, queries: [], fields: definition.fields, multi: !!definition.multi}); | 357 | return 1; |
807 | var formName = qtf.get('formName'); | 358 | } else if (b === 'after') { |
808 | if (formName) { | 359 | return -1; |
809 | formNameMap[formName] = qtf; | ||
810 | } | 360 | } |
811 | queryFields.set(queryName, qtf); | ||
812 | }, this); | ||
813 | return _.extend(_.result(SolrSearch.__super__, 'defaults'), { | ||
814 | initializing: true, | ||
815 | initialized: false, | ||
816 | query: '', | ||
817 | formNameMap: formNameMap, | ||
818 | results: new Backbone.Collection(), | ||
819 | ordering: new Ordering({items: constructor.orderingItems}, {parse: true}), | ||
820 | facets: facets, | ||
821 | queryFields: queryFields, | ||
822 | }); | ||
823 | }, | ||
824 | applyQueryParameters: function() { | ||
825 | var skipOptions = {skipSearch: true}; | ||
826 | var parts = document.location.href.match(/.*\?(.*)/); | ||
827 | var facets = this.get('facets'); | ||
828 | facets.resetSearch(); | ||
829 | _.each(this.get('queryFields').values(), function(qtf) { | ||
830 | qtf.set({ | ||
831 | query: null, | ||
832 | queries: [], | ||
833 | }, skipOptions); | ||
834 | }); | ||
835 | |||
836 | if (parts) { | ||
837 | var formNameMap = this.get('formNameMap'); | ||
838 | var keyValueParts = parts[1].split('&'); | ||
839 | _.each(keyValueParts, function(keyValuePart, i) { | ||
840 | var keyFieldValue = keyValuePart.match(/^([^.]+)(?:\.([^.]+))?=(.*)$/); | ||
841 | if (keyFieldValue) { | ||
842 | var key = keyFieldValue[1]; | ||
843 | var field = keyFieldValue[2]; | ||
844 | var value = keyFieldValue[3]; | ||
845 | value = value.replace(/(\+|%20)/g, ' '); | ||
846 | var impl = formNameMap[key]; | ||
847 | if (impl) { | ||
848 | if (impl instanceof QueryTextField) { | ||
849 | impl.set('query', value, skipOptions); | ||
850 | } else if (impl.facetType === 'field' && value) { | ||
851 | if (field === 'min') { | ||
852 | impl.set('queryMin', parseInt(value), skipOptions); | ||
853 | } else if (field === 'max') { | ||
854 | impl.set('queryMax', parseInt(value), skipOptions); | ||
855 | } else if (field === 'items') { | ||
856 | var items = impl.get('items'); | ||
857 | var item = items.get(value); | ||
858 | if (!item) { | ||
859 | item = new SolrFacets.Facet.Item({key: value, value: 0}); | ||
860 | items.add(item); | ||
861 | item.on('change:checked', function(model, value, options) { | ||
862 | this.trigger('item-change', null, null, options); | ||
863 | impl.trigger('item-change', null, null, options); | ||
864 | }, facets); | ||
865 | } | ||
866 | item.set('checked', true, skipOptions); | ||
867 | } | ||
868 | } | ||
869 | } | ||
870 | } | ||
871 | }, this); | ||
872 | } | 361 | } |
362 | return comparator.call(this, a, b); | ||
363 | }; | ||
364 | } | ||
365 | var Facet = Facets.Facet = Backbone.Model.extend({ | ||
366 | defaults: function defaults() { | ||
367 | return { | ||
368 | formName: null, | ||
369 | query: null, | ||
370 | suggestions: [], | ||
371 | }; | ||
873 | }, | 372 | }, |
874 | initialize: function(data, options) { | 373 | initialize: function(data, options) { |
875 | options = (options || {}); | 374 | this.set('all', true); |
876 | this.set('options', new Backbone.Model({ | 375 | this.on('change:queryMax change:queryMin', function() { |
877 | faceting: !!options.faceting, | 376 | this.set('hasQuery', this.get('queryMin') !== undefined && this.get('queryMax') !== undefined); |
878 | highlighting: !!options.highlighting, | ||
879 | showAll: !!options.showAll, | ||
880 | })); | ||
881 | this._doSearchImmediately = _.bind(function(options) { | ||
882 | this.fetch(_.extend({}, options, { | ||
883 | data: this.toJSON(), | ||
884 | merge: false, | ||
885 | traditional: true, | ||
886 | })); | ||
887 | }, this); | 377 | }, this); |
888 | this._doSearch = _.debounce(this._doSearchImmediately, 250); | 378 | (function(self) { |
889 | this._doSearch = (function(doSearch) { | 379 | var skipCallback; |
890 | return function(options) { | 380 | self.on('item-change change:queryMax change:queryMin', function() { |
891 | var skipSearch = this._skipSearch || (options || {}).skipSearch; | 381 | switch (skipCallback) { |
892 | if (!skipSearch) { | 382 | case 'change:checkedKeys': |
893 | doSearch(options); | 383 | case 'change:all': |
384 | return; | ||
894 | } | 385 | } |
895 | }; | 386 | var checkedKeys = []; |
896 | })(this._doSearch); | 387 | this.get('items').each(function(facetItem) { |
897 | _.each(this.get('queryFields').values(), function(subQueryModel) { | 388 | if (facetItem.get('checked')) { |
898 | subQueryModel.on('change:query change:queries', function(model, value, options) { | 389 | checkedKeys.push(facetItem.get('key')); |
899 | this.trigger('change:queryFields', null, null, options); | 390 | } |
900 | }, this); | 391 | }); |
901 | }, this); | 392 | var checkedCount = checkedKeys.length; |
902 | this.on('change:queryFields', function(model, value, options) { | 393 | if (this.get('queryMin') !== undefined && this.get('queryMax') !== undefined) { |
903 | this.trigger('change:query', null, null, options); | 394 | checkedCount++; |
904 | }, this); | 395 | } |
905 | this.get('options').on('change:faceting change:highlighting', function(model, value, options) { | 396 | skipCallback = 'item-change'; |
906 | this._doSearch(options); | 397 | try { |
907 | }, this); | 398 | this.set({ |
908 | this.on('change:query', function(model, value, options) { | 399 | all: checkedCount === 0, |
909 | // this is silent, which causes the change events to not fire; | 400 | checkedKeys: checkedKeys, |
910 | // this is ok, because the next .on will also see the change | ||
911 | // event on query, and resend the search | ||
912 | this._doSearch(_.extend({}, options, {resetCurrentPage: true, resetFacets: true, resetOrdering: true})); | ||
913 | }, this); | ||
914 | this.on('change:pageSize', function(model, value, options) { | ||
915 | this._doSearch(_.extend({}, options, {resetCurrentPage: true})); | ||
916 | }, this); | ||
917 | this.on('change:currentPage', function(model, value, options) { | ||
918 | this._doSearch(options); | ||
919 | }, this); | ||
920 | this.get('facets').on('item-change', function(model, value, options) { | ||
921 | this._doSearch(_.extend({}, options, {resetCurrentPage: true})); | ||
922 | }, this); | ||
923 | this.get('ordering').on('change:value', function(model, value, options) { | ||
924 | this._doSearch(_.extend({}, options, {resetCurrentPage: true})); | ||
925 | }, this); | ||
926 | return SolrSearch.__super__.initialize.apply(this, arguments); | ||
927 | |||
928 | }, | ||
929 | parse: function parse(data, options) { | ||
930 | if (options.facetSuggestions) { | ||
931 | return null; | ||
932 | } | ||
933 | var skipOptions = _.extend({}, options, {skipSearch: true}); | ||
934 | if (options.resetCurrentPage) { | ||
935 | this.set('currentPage', 1, skipOptions); | ||
936 | } | ||
937 | if (options.resetFacets) { | ||
938 | this.get('facets').resetSearch(skipOptions); | ||
939 | } | ||
940 | if (options.resetOrdering) { | ||
941 | this.get('ordering').set('value', this.get('ordering').get('items').at(0).get('value'), skipOptions); | ||
942 | } | ||
943 | var facets = this.get('facets'); | ||
944 | if (this.get('options').get('faceting')) { | ||
945 | facets.applyFacetResults(data); | ||
946 | } else { | ||
947 | facets.resetSearch(options); | ||
948 | } | ||
949 | var list = []; | ||
950 | var highlighting = this.get('options').get('highlighting') ? data.highlighting : {}; | ||
951 | |||
952 | var self = this; | ||
953 | _.each(data.response.docs, function(doc, index) { | ||
954 | var itemHighlighting = highlighting[doc.id]; | ||
955 | if (!itemHighlighting) { | ||
956 | itemHighlighting = {}; | ||
957 | } | ||
958 | list.push(self.searchParseDoc(doc, index, itemHighlighting)); | ||
959 | }); | ||
960 | 401 | ||
961 | return { | 402 | }); |
962 | initializing: false, | 403 | } finally { |
963 | initialized: true, | 404 | skipCallback = null; |
964 | results: new Backbone.Collection(list), | 405 | } |
965 | facets: facets, | 406 | }, self); |
966 | options: this.get('options'), | 407 | self.on('change:all', function() { |
967 | totalCount: data.response.numFound, | 408 | switch (skipCallback) { |
968 | queryTime: (data.responseHeader.QTime / 1000).toFixed(2), | 409 | case 'change:checkedKeys': |
969 | hasResults: list.length > 0, | 410 | case 'item-change': |
970 | }; | 411 | return; |
971 | }, | 412 | } |
972 | searchParseDoc: function searchParseDoc(doc, index, itemHighlighting) { | 413 | skipCallback = 'change:all'; |
973 | var fieldsToParse = mergeStaticProps(this.constructor, SolrSearch, {}, 'parsedFieldMap'); | 414 | try { |
974 | var result = {}; | 415 | this.get('items').invoke('set', {checked: false}); |
975 | _.each(fieldsToParse, function(value, key) { | 416 | this.set({ |
976 | var parsed; | 417 | checkedKeys: [], |
977 | if (_.isFunction(value)) { | 418 | queryMax: undefined, |
978 | parsed = value.call(this, doc, index, itemHighlighting, key); | 419 | queryMin: undefined, |
979 | } else if (_.isArray(value)) { | 420 | }); |
980 | parsed = []; | 421 | } finally { |
981 | _.each(value, function(item, i) { | 422 | skipCallback = null; |
982 | var rawValue = doc[item.name]; | 423 | } |
983 | if (rawValue) { | 424 | }, self); |
984 | var parsedValue = item.parse.call(this, doc, index, itemHighlighting, rawValue, item.name); | 425 | self.on('change:checkedKeys', function() { |
985 | if (parsedValue) { | 426 | switch (skipCallback) { |
986 | parsed.push(parsedValue); | 427 | case 'item-change': |
428 | case 'change:all': | ||
429 | return; | ||
430 | } | ||
431 | var checkedKeys = {}; | ||
432 | _.each(this.get('checkedKeys'), function(value, i) { | ||
433 | checkedKeys[value] = true; | ||
434 | }); | ||
435 | skipCallback = 'change:checkedKeys'; | ||
436 | var checkedCount = 0; | ||
437 | if (this.get('queryMin') !== undefined && this.get('queryMax') !== undefined) { | ||
438 | checkedCount++; | ||
439 | } | ||
440 | try { | ||
441 | this.get('items').each(function(facetItem) { | ||
442 | var newChecked = checkedKeys[facetItem.get('key')]; | ||
443 | var currentChecked = facetItem.get('checked'); | ||
444 | if (newChecked !== currentChecked) { | ||
445 | facetItem.set('checked', newChecked); | ||
987 | } | 446 | } |
988 | } | 447 | if (facetItem.get('checked')) { |
989 | }, this); | 448 | checkedCount++; |
990 | } else { | 449 | } |
991 | parsed = doc[value]; | 450 | }); |
992 | } | 451 | this.set({ |
993 | result[key] = parsed; | 452 | all: checkedCount === 0, |
994 | }, this); | 453 | }); |
995 | return result; | 454 | } finally { |
996 | }, | 455 | skipCallback = null; |
997 | toJSON: function(options) { | 456 | } |
998 | if (!options) { | 457 | }, self); |
999 | options = {}; | 458 | })(this); |
1000 | } | 459 | var sortKeyExtractor = options.sortKeyExtractor; |
1001 | var facets = this.get('facets'); | 460 | if (!sortKeyExtractor) { |
1002 | var constructor = this.constructor; | 461 | sortKeyExtractor = getItemKeyAccessor; |
1003 | var result = { | ||
1004 | defType: 'edismax', | ||
1005 | qf: constructor.queryField, | ||
1006 | wt: 'json', | ||
1007 | }; | ||
1008 | var ordering = this.get('ordering'); | ||
1009 | var sort = ordering.get('value'); | ||
1010 | if (options.resetOrdering) { | ||
1011 | sort = ordering.get('items').at(0).get('value'); | ||
1012 | } | 462 | } |
1013 | var currentPage = this.get('currentPage'); | 463 | var comparator = options.comparator; |
1014 | var pageSize = this.get('pageSize'); | 464 | if (!comparator) { |
1015 | if (options.resetCurrentPage) { | 465 | comparator = Sort.standard; |
1016 | currentPage = 1; | 466 | } else if (typeof comparator === 'string') { |
467 | comparator = Sort[comparator]; | ||
1017 | } | 468 | } |
1018 | result.sort = sort; | 469 | switch (options.facetType) { |
1019 | result.rows = pageSize; | 470 | case 'year': |
1020 | result.start = (currentPage - 1) * pageSize; | 471 | case 'range': |
1021 | result.fl = mergeStaticSets(constructor, SolrSearch, [], 'returnFields'); | 472 | case 'interval': |
1022 | if (this.get('options').get('highlighting')) { | 473 | comparator = wrapComparatorForAllBeforeAfter(comparator, ['all', 'before'].indexOf(options.other) !== -1, ['all', 'after'].indexOf(options.other) !== -1); |
1023 | result.hl = true; | 474 | break; |
1024 | result['hl.fl'] = ['content', 'title'].concat(constructor.extraHighlightFields); | ||
1025 | } | 475 | } |
1026 | 476 | var formatter = options.formatter; | |
1027 | if (this.get('options').get('faceting')) { | 477 | if (!formatter) { |
1028 | var facetFormData = facets.getFacetFormData(); | 478 | var labelMap = options.labelMap; |
1029 | var fq = facetFormData.fq; | 479 | if (labelMap) { |
1030 | delete(facetFormData.fq); | 480 | formatter = function(item) { |
1031 | result = _.extend(result, facetFormData); | 481 | var value = item.get('key'); |
1032 | if (fq.length) { | 482 | var label = options.labelMap[value.toUpperCase()]; |
1033 | if (result.fq) { | 483 | return label ? label : value; |
1034 | result.fq = result.fq.concat(fq); | 484 | }; |
1035 | } else { | 485 | } else { |
1036 | result.fq = fq; | 486 | formatter = getItemKeyAccessor; |
1037 | } | ||
1038 | } | 487 | } |
1039 | } | 488 | } |
1040 | 489 | this.formatter = formatter; | |
1041 | var queryParts = []; | 490 | comparator = (function(comparator) { |
1042 | var queryFields = this.get('queryFields'); | 491 | return function facetItemValue(a, b) { |
1043 | _.each(queryFields.keys(), function(queryName) { | 492 | var keyA = sortKeyExtractor(a); |
1044 | var subQueryModel = queryFields.get(queryName); | 493 | var keyB = sortKeyExtractor(b); |
1045 | var fields = subQueryModel.get('fields'); | 494 | switch (this.get('orderByDirection')) { |
1046 | var queries = subQueryModel.get('queries'); | 495 | case 'desc': |
1047 | var query = subQueryModel.get('query'); | 496 | var tmp = keyA; |
1048 | if (query) { | 497 | keyA = keyB; |
1049 | queries = queries.concat([query]); | 498 | keyB = tmp; |
1050 | } | 499 | break; |
1051 | var subQuery = []; | ||
1052 | for (var i = 0; i < fields.length; i++) { | ||
1053 | var fieldName = fields[i]; | ||
1054 | for (var j = 0; j < queries.length; j++) { | ||
1055 | // FIXME: quote the query | ||
1056 | subQuery.push(fieldName + ':\'' + queries[j] + '\''); | ||
1057 | } | 500 | } |
1058 | } | 501 | return comparator.call(this, sortKeyExtractor(a), sortKeyExtractor(b)); |
1059 | if (subQuery.length) { | 502 | }; |
1060 | queryParts.push(subQuery.join(' OR ')); | 503 | })(comparator); |
1061 | } | 504 | this.set('items', new ItemCollection([], {comparator: comparator})); |
1062 | }, this); | 505 | this.facetType = options.facetType; |
1063 | 506 | this.facetStats = options.facetStats; | |
1064 | var query = queryParts.join(' AND '); | 507 | this.other = options.other; |
1065 | if (!query) { | 508 | this.facetField = options.facetField; |
1066 | query = '*:*'; | 509 | this.queryField = options.queryField; |
1067 | if (!this.get('options').get('showAll') && !result.fq) { | 510 | this.queryValue = options.queryValue; |
1068 | result.rows = 0; | 511 | this.bins = options.bins; |
1069 | } | 512 | switch (this.facetType) { |
513 | case 'year': | ||
514 | this.rangeStart = options.start; | ||
515 | this.rangeEnd = options.end; | ||
516 | this.rangeGap = '+' + (options.gap ? options.gap : 1) + 'YEAR'; | ||
517 | break; | ||
518 | case 'range': | ||
519 | this.rangeStart = options.start; | ||
520 | this.rangeEnd = options.end; | ||
521 | this.rangeGap = options.gap; | ||
522 | break; | ||
523 | case 'field': | ||
524 | case 'year-field': | ||
525 | case 'interval': | ||
526 | break; | ||
527 | default: | ||
528 | var e = new Error('Unsupported facet type: ' + this.facetType); | ||
529 | e.facet = this; | ||
530 | throw e; | ||
1070 | } | 531 | } |
1071 | var dateBoostField = constructor.dateBoostField; | 532 | return Facet.__super__.initialize.apply(this, arguments); |
1072 | var popularityBoostField = constructor.popularityBoostField; | 533 | }, |
1073 | if (result.rows !== 0 && (dateBoostField || popularityBoostField)) { | 534 | /* |
1074 | var boostSum = []; | 535 | toJSON: function toJSON(options) { |
1075 | if (dateBoostField) { | 536 | if (!!options.facetSuggestions) { |
1076 | result.dateBoost = 'recip(ms(NOW,' + dateBoostField + '),3.16e-11,1,1)'; | 537 | return { |
1077 | boostSum.push('$dateBoost'); | 538 | rows: 0, |
1078 | } | 539 | facet: true, |
1079 | if (popularityBoostField) { | 540 | wt: 'json', |
1080 | result.popularityBoost = 'def(' + popularityBoostField + ',0)'; | 541 | q: '*:*', |
1081 | boostSum.push('$popularityBoost'); | 542 | 'facet.contains': this.get('query'), |
1082 | } | 543 | 'facet.contains.ignoreCase': true, |
1083 | result.qq = query; | 544 | 'facet.field': this.queryField, |
1084 | result.q = '{!boost b=sum(' + boostSum.join(',') + ') v=$qq defType=$defType}'; | 545 | 'facet.mincount': 1, |
1085 | } else { | 546 | }; |
1086 | result.q = query; | ||
1087 | } | 547 | } |
1088 | return result; | ||
1089 | }, | 548 | }, |
1090 | }, { | 549 | parse: function parse(data, options) { |
1091 | cleanup: function cleanup(txt) { | 550 | if (!!options.facetSuggestions) { |
1092 | if (txt) { | 551 | var suggestions = []; |
1093 | txt = txt.replace(/&/g, '&'); | 552 | var key; |
1094 | txt = txt.replace(/'/g, '\''); | 553 | _.each(data.facet_counts.facet_fields[this.queryField], function(value, index) { |
1095 | txt = txt.replace(/[^a-zA-Z0-9 -\.,:;%<>\/'"|]/g, ' '); | 554 | if (index % 2 === 0) { |
555 | key = value; | ||
556 | } else if (value > 0) { | ||
557 | suggestions.push({key: key, value: value}); | ||
558 | } | ||
559 | }); | ||
560 | return { | ||
561 | suggestions: suggestions, | ||
562 | }; | ||
1096 | } | 563 | } |
1097 | return txt; | 564 | return null; |
1098 | }, | 565 | }, |
1099 | returnFields: [ | 566 | */ |
1100 | 'keywords', | 567 | }); |
1101 | 'last_modified', | 568 | var ItemCollection = Backbone.Collection.extend({ |
1102 | 'path', | 569 | remove: function() { |
1103 | 'score', | 570 | } |
1104 | 'title', | 571 | }); |
1105 | ], | 572 | var Item = Facet.Item = Backbone.Model.extend({ |
1106 | parsedFieldMap: { | 573 | idAttribute: 'key', |
1107 | content: function(doc, index, itemHighlighting) { | 574 | defaults: { |
1108 | var content = itemHighlighting.content; | 575 | checked: false, |
1109 | if (content && content.constructor === Array) { | 576 | hidden: false, |
1110 | content = content.join(' '); | ||
1111 | } | ||
1112 | if (!content) { | ||
1113 | content = ''; | ||
1114 | } | ||
1115 | return this.constructor.cleanup(content); | ||
1116 | }, | ||
1117 | keywords: 'keywords', | ||
1118 | lastModified: 'last_modified', | ||
1119 | path: 'url', | ||
1120 | score: 'score', | ||
1121 | title: function(doc, index, itemHighlighting) { | ||
1122 | var title; | ||
1123 | if (itemHighlighting.title) { | ||
1124 | title = itemHighlighting.title.join(' '); | ||
1125 | } else { | ||
1126 | title = doc.title[0]; | ||
1127 | } | ||
1128 | return this.constructor.cleanup(title); | ||
1129 | }, | ||
1130 | }, | 577 | }, |
578 | _initialize: function(data, options) { | ||
579 | this.on('add', function(model, parent, options) { | ||
580 | // added to a collection | ||
581 | this.on('change:checked', function() { | ||
582 | parent.trigger('item-change'); | ||
583 | }); | ||
584 | }, this); | ||
585 | return Item.__super__.initialize.apply(this, arguments); | ||
586 | } | ||
1131 | }); | 587 | }); |
1132 | SolrSearch.Facets = SolrFacets; | 588 | |
1133 | SolrSearch.Pagination = Pagination; | 589 | return Facets; |
1134 | return SolrSearch; | ||
1135 | }); | 590 | }); | ... | ... |
src/scripts/solr/model/Ordering.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | var Backbone = require('backbone'); | ||
4 | |||
5 | var Ordering = Backbone.Model.extend({ | ||
6 | defaults: { | ||
7 | value: null, | ||
8 | items: new Backbone.Collection(), | ||
9 | }, | ||
10 | initialize: function(data, options) { | ||
11 | if (this.get('value') === null) { | ||
12 | var firstItem = this.get('items').at(0); | ||
13 | if (firstItem) { | ||
14 | this.set('value', firstItem.get('value')); | ||
15 | } | ||
16 | } | ||
17 | }, | ||
18 | parse: function(data) { | ||
19 | var result = _.clone(data); | ||
20 | if (result.items && !(result.items instanceof Backbone.Collection)) { | ||
21 | result.items = new Backbone.Collection(result.items, {parse: true}); | ||
22 | } | ||
23 | return result; | ||
24 | }, | ||
25 | }); | ||
26 | return Ordering; | ||
27 | }); |
src/scripts/solr/model/Pagination.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | var Backbone = require('backbone'); | ||
4 | |||
5 | function stepFalse(e) { | ||
6 | e.preventDefault(); | ||
7 | return false; | ||
8 | } | ||
9 | var Pagination = Backbone.Model.extend({ | ||
10 | defaults: { | ||
11 | currentPage: 1, | ||
12 | hasNext: false, | ||
13 | hasNextJumpPage: false, | ||
14 | hasPrevious: false, | ||
15 | hasPreviousJumpPage: false, | ||
16 | nextJumpPage: undefined, | ||
17 | nextPage: undefined, | ||
18 | pages: [], | ||
19 | pageJump: 5, | ||
20 | pageSize: 10, | ||
21 | previousJumpPage: undefined, | ||
22 | previousPage: undefined, | ||
23 | totalCount: 0, | ||
24 | totalPages: 0, | ||
25 | }, | ||
26 | initialize: function(data, options) { | ||
27 | var buildPages = function buildPages() { | ||
28 | var currentPage = parseInt(this.get('currentPage')); | ||
29 | var pageSize = parseInt(this.get('pageSize')); | ||
30 | var totalCount = parseInt(this.get('totalCount')); | ||
31 | if (!currentPage || !pageSize || !totalCount) { | ||
32 | return; | ||
33 | } | ||
34 | var pages = []; | ||
35 | var totalPages = Math.floor((totalCount + pageSize - 1) / pageSize); | ||
36 | function addPage(self, i) { | ||
37 | pages.push({ | ||
38 | current: i === currentPage, | ||
39 | jump: function() { | ||
40 | self.set('currentPage', i); | ||
41 | return true; | ||
42 | }, | ||
43 | number: i, | ||
44 | }); | ||
45 | } | ||
46 | var startAt = currentPage - 4, endAt = currentPage + 5; | ||
47 | if (startAt < 1) { | ||
48 | endAt += (1 - startAt); | ||
49 | } | ||
50 | if (endAt > totalPages) { | ||
51 | startAt -= endAt - totalPages - 1; | ||
52 | endAt = totalPages + 1; | ||
53 | } | ||
54 | if (startAt < 1) { | ||
55 | startAt = 1; | ||
56 | } | ||
57 | if (endAt - startAt < 9) { | ||
58 | /* global console:false */ | ||
59 | console.log('foo'); | ||
60 | } | ||
61 | |||
62 | for (var i = startAt; i < endAt; i++) { | ||
63 | if (i > 0 && i <= totalPages) { | ||
64 | addPage(this, i); | ||
65 | } | ||
66 | } | ||
67 | var hasPrevious = currentPage > 1; | ||
68 | var hasNext = currentPage < totalPages; | ||
69 | var pageJump = this.get('pageJump'); | ||
70 | var nextJumpPage, previousJumpPage, hasNextJump, hasPreviousJump; | ||
71 | if (pageJump) { | ||
72 | nextJumpPage = currentPage + pageJump; | ||
73 | previousJumpPage = currentPage - pageJump; | ||
74 | hasNextJump = nextJumpPage < totalPages; | ||
75 | hasPreviousJump = previousJumpPage > 0; | ||
76 | } else { | ||
77 | hasNextJump = false; | ||
78 | hasPreviousJump = false; | ||
79 | } | ||
80 | this.set({ | ||
81 | hasNext: hasNext, | ||
82 | hasNextJump: hasNextJump, | ||
83 | hasPrevious: hasPrevious, | ||
84 | hasPreviousJump: hasPreviousJump, | ||
85 | nextJumpPage: hasNextJump ? nextJumpPage : undefined, | ||
86 | nextPage: hasNext ? currentPage + 1 : undefined, | ||
87 | pages: pages, | ||
88 | previousJumpPage: hasNextJump ? previousJumpPage : undefined, | ||
89 | previousPage: hasPrevious ? currentPage - 1 : undefined, | ||
90 | totalPages: totalPages, | ||
91 | }); | ||
92 | }; | ||
93 | this.on('change:pageSize change:currentPage change:totalCount', buildPages, this); | ||
94 | var installActions = _.bind(function installActions(eventName, nextName, hasNextName, previousName, hasPreviousName, pageCount) { | ||
95 | this.on(eventName, function() { | ||
96 | var hasNext = this.get(hasNextName); | ||
97 | var hasPrevious = this.get(hasPreviousName); | ||
98 | var next, previous; | ||
99 | if (hasNext) { | ||
100 | next = _.bind(function(e) { | ||
101 | e.preventDefault(); | ||
102 | this.set('currentPage', this.get('currentPage') + pageCount); | ||
103 | return false; | ||
104 | }, this); | ||
105 | } else { | ||
106 | next = stepFalse; | ||
107 | } | ||
108 | if (hasPrevious) { | ||
109 | previous = _.bind(function(e) { | ||
110 | e.preventDefault(); | ||
111 | this.set('currentPage', this.get('currentPage') - pageCount); | ||
112 | return false; | ||
113 | }, this); | ||
114 | } else { | ||
115 | previous = stepFalse; | ||
116 | } | ||
117 | this.set(nextName, next); | ||
118 | this.set(previousName, previous); | ||
119 | }, this); | ||
120 | this[nextName] = _.bind(function() { | ||
121 | return this.get(nextName)(); | ||
122 | }, this); | ||
123 | this[previousName] = _.bind(function() { | ||
124 | return this.get(previousName)(); | ||
125 | }, this); | ||
126 | }, this); | ||
127 | this.on('change:pageJump', function() { | ||
128 | var pageJump = this.get('pageJump'); | ||
129 | installActions('change:hasNextJump change:hasPreviousJump', 'nextJump', 'hasNextJump', 'previousJump', 'hasPreviousJump', pageJump); | ||
130 | }, this); | ||
131 | installActions('change:hasNext change:hasPrevious', 'next', 'hasNext', 'previous', 'hasPrevious', 1); | ||
132 | buildPages.apply(this); | ||
133 | this.trigger('change:pageJump'); | ||
134 | return Pagination.__super__.initialize.apply(this, arguments); | ||
135 | } | ||
136 | }); | ||
137 | return Pagination; | ||
138 | }); |
src/scripts/solr/model/QueryTextField.js
0 → 100644
src/scripts/solr/model/Solr.js
0 → 100644
1 | define(function(require) { | ||
2 | 'use strict'; | ||
3 | var _ = require('underscore'); | ||
4 | var Backbone = require('backbone'); | ||
5 | |||
6 | var Facets = require('solr/model/Facets'); | ||
7 | var Ordering = require('solr/model/Ordering'); | ||
8 | var Pagination = require('solr/model/Pagination'); | ||
9 | var QueryTextField = require('solr/model/QueryTextField'); | ||
10 | |||
11 | //var module = require('module'); | ||
12 | |||
13 | function mergeStatic(startPtr, endPtr, obj, fieldName, filterFunc) { | ||
14 | var result = filterFunc(obj); | ||
15 | var ptr = startPtr; | ||
16 | while (true) { | ||
17 | result = _.extend(result, filterFunc(_.result(ptr, fieldName))); | ||
18 | if (ptr === endPtr) { | ||
19 | break; | ||
20 | } | ||
21 | ptr = ptr.__super__.constructor; | ||
22 | } | ||
23 | return result; | ||
24 | } | ||
25 | |||
26 | function mergeStaticProps(startPtr, endPtr, obj, fieldName) { | ||
27 | return mergeStaticFunc(startPtr, endPtr, obj, fieldName, _.identity); | ||
28 | } | ||
29 | |||
30 | function mergeStaticSets(startPtr, endPtr, obj, fieldName) { | ||
31 | return _.keys(mergeStaticFunc(startPtr, endPtr, obj, fieldName, function arrayToMap(array) { | ||
32 | var result = {}; | ||
33 | _.each(array, function(value) { | ||
34 | result[value] = true; | ||
35 | }); | ||
36 | return result; | ||
37 | })); | ||
38 | } | ||
39 | |||
40 | function getItemKeyAccessor(item) { | ||
41 | return item.get('key'); | ||
42 | } | ||
43 | var Solr = Pagination.extend({ | ||
44 | url: function url() { | ||
45 | return this.constructor.selectUrl; | ||
46 | }, | ||
47 | defaults: function defaults() { | ||
48 | var constructor = this.constructor; | ||
49 | var formNameMap = {}; | ||
50 | var facets = new Facets(this.constructor.facets, {search: this}); | ||
51 | _.each(facets.values(), function(facet) { | ||
52 | var formName = facet.get('formName'); | ||
53 | if (formName) { | ||
54 | formNameMap[formName] = facet; | ||
55 | } | ||
56 | }); | ||
57 | var queryFields = new Backbone.Model(); | ||
58 | _.each(constructor.queryTextFields, function(definition, queryName) { | ||
59 | var qtf = new QueryTextField({formName: definition.formName, name: queryName, queries: [], fields: definition.fields, multi: !!definition.multi}); | ||
60 | var formName = qtf.get('formName'); | ||
61 | if (formName) { | ||
62 | formNameMap[formName] = qtf; | ||
63 | } | ||
64 | queryFields.set(queryName, qtf); | ||
65 | }, this); | ||
66 | return _.extend(_.result(Solr.__super__, 'defaults'), { | ||
67 | initializing: true, | ||
68 | initialized: false, | ||
69 | query: '', | ||
70 | formNameMap: formNameMap, | ||
71 | results: new Backbone.Collection(), | ||
72 | ordering: new Ordering({items: constructor.orderingItems}, {parse: true}), | ||
73 | facets: facets, | ||
74 | queryFields: queryFields, | ||
75 | }); | ||
76 | }, | ||
77 | applyQueryParameters: function() { | ||
78 | var skipOptions = {skipSearch: true}; | ||
79 | var parts = document.location.href.match(/.*\?(.*)/); | ||
80 | var facets = this.get('facets'); | ||
81 | facets.resetSearch(); | ||
82 | _.each(this.get('queryFields').values(), function(qtf) { | ||
83 | qtf.set({ | ||
84 | query: null, | ||
85 | queries: [], | ||
86 | }, skipOptions); | ||
87 | }); | ||
88 | |||
89 | if (parts) { | ||
90 | var formNameMap = this.get('formNameMap'); | ||
91 | var keyValueParts = parts[1].split('&'); | ||
92 | _.each(keyValueParts, function(keyValuePart, i) { | ||
93 | var keyFieldValue = keyValuePart.match(/^([^.]+)(?:\.([^.]+))?=(.*)$/); | ||
94 | if (keyFieldValue) { | ||
95 | var key = keyFieldValue[1]; | ||
96 | var field = keyFieldValue[2]; | ||
97 | var value = keyFieldValue[3]; | ||
98 | value = value.replace(/(\+|%20)/g, ' '); | ||
99 | var impl = formNameMap[key]; | ||
100 | if (impl) { | ||
101 | if (impl instanceof QueryTextField) { | ||
102 | impl.set('query', value, skipOptions); | ||
103 | } else if (impl.facetType === 'field' && value) { | ||
104 | if (field === 'min') { | ||
105 | impl.set('queryMin', parseInt(value), skipOptions); | ||
106 | } else if (field === 'max') { | ||
107 | impl.set('queryMax', parseInt(value), skipOptions); | ||
108 | } else if (field === 'items') { | ||
109 | var items = impl.get('items'); | ||
110 | var item = items.get(value); | ||
111 | if (!item) { | ||
112 | item = new Facets.Facet.Item({key: value, value: 0}); | ||
113 | items.add(item); | ||
114 | item.on('change:checked', function(model, value, options) { | ||
115 | this.trigger('item-change', null, null, options); | ||
116 | impl.trigger('item-change', null, null, options); | ||
117 | }, facets); | ||
118 | } | ||
119 | item.set('checked', true, skipOptions); | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | }, this); | ||
125 | } | ||
126 | }, | ||
127 | initialize: function(data, options) { | ||
128 | options = (options || {}); | ||
129 | this.set('options', new Backbone.Model({ | ||
130 | faceting: !!options.faceting, | ||
131 | highlighting: !!options.highlighting, | ||
132 | showAll: !!options.showAll, | ||
133 | })); | ||
134 | this._doSearchImmediately = _.bind(function(options) { | ||
135 | this.fetch(_.extend({}, options, { | ||
136 | data: this.toJSON(), | ||
137 | merge: false, | ||
138 | traditional: true, | ||
139 | })); | ||
140 | }, this); | ||
141 | this._doSearch = _.debounce(this._doSearchImmediately, 250); | ||
142 | this._doSearch = (function(doSearch) { | ||
143 | return function(options) { | ||
144 | var skipSearch = this._skipSearch || (options || {}).skipSearch; | ||
145 | if (!skipSearch) { | ||
146 | doSearch(options); | ||
147 | } | ||
148 | }; | ||
149 | })(this._doSearch); | ||
150 | _.each(this.get('queryFields').values(), function(subQueryModel) { | ||
151 | subQueryModel.on('change:query change:queries', function(model, value, options) { | ||
152 | this.trigger('change:queryFields', null, null, options); | ||
153 | }, this); | ||
154 | }, this); | ||
155 | this.on('change:queryFields', function(model, value, options) { | ||
156 | this.trigger('change:query', null, null, options); | ||
157 | }, this); | ||
158 | this.get('options').on('change:faceting change:highlighting', function(model, value, options) { | ||
159 | this._doSearch(options); | ||
160 | }, this); | ||
161 | this.on('change:query', function(model, value, options) { | ||
162 | // this is silent, which causes the change events to not fire; | ||
163 | // this is ok, because the next .on will also see the change | ||
164 | // event on query, and resend the search | ||
165 | this._doSearch(_.extend({}, options, {resetCurrentPage: true, resetFacets: true, resetOrdering: true})); | ||
166 | }, this); | ||
167 | this.on('change:pageSize', function(model, value, options) { | ||
168 | this._doSearch(_.extend({}, options, {resetCurrentPage: true})); | ||
169 | }, this); | ||
170 | this.on('change:currentPage', function(model, value, options) { | ||
171 | this._doSearch(options); | ||
172 | }, this); | ||
173 | this.get('facets').on('item-change', function(model, value, options) { | ||
174 | this._doSearch(_.extend({}, options, {resetCurrentPage: true})); | ||
175 | }, this); | ||
176 | this.get('ordering').on('change:value', function(model, value, options) { | ||
177 | this._doSearch(_.extend({}, options, {resetCurrentPage: true})); | ||
178 | }, this); | ||
179 | return Solr.__super__.initialize.apply(this, arguments); | ||
180 | |||
181 | }, | ||
182 | parse: function parse(data, options) { | ||
183 | if (options.facetSuggestions) { | ||
184 | return null; | ||
185 | } | ||
186 | var skipOptions = _.extend({}, options, {skipSearch: true}); | ||
187 | if (options.resetCurrentPage) { | ||
188 | this.set('currentPage', 1, skipOptions); | ||
189 | } | ||
190 | if (options.resetFacets) { | ||
191 | this.get('facets').resetSearch(skipOptions); | ||
192 | } | ||
193 | if (options.resetOrdering) { | ||
194 | this.get('ordering').set('value', this.get('ordering').get('items').at(0).get('value'), skipOptions); | ||
195 | } | ||
196 | var facets = this.get('facets'); | ||
197 | if (this.get('options').get('faceting')) { | ||
198 | facets.applyFacetResults(data); | ||
199 | } else { | ||
200 | facets.resetSearch(options); | ||
201 | } | ||
202 | var list = []; | ||
203 | var highlighting = this.get('options').get('highlighting') ? data.highlighting : {}; | ||
204 | |||
205 | var self = this; | ||
206 | _.each(data.response.docs, function(doc, index) { | ||
207 | var itemHighlighting = highlighting[doc.id]; | ||
208 | if (!itemHighlighting) { | ||
209 | itemHighlighting = {}; | ||
210 | } | ||
211 | list.push(self.searchParseDoc(doc, index, itemHighlighting)); | ||
212 | }); | ||
213 | |||
214 | return { | ||
215 | initializing: false, | ||
216 | initialized: true, | ||
217 | results: new Backbone.Collection(list), | ||
218 | facets: facets, | ||
219 | options: this.get('options'), | ||
220 | totalCount: data.response.numFound, | ||
221 | queryTime: (data.responseHeader.QTime / 1000).toFixed(2), | ||
222 | hasResults: list.length > 0, | ||
223 | }; | ||
224 | }, | ||
225 | searchParseDoc: function searchParseDoc(doc, index, itemHighlighting) { | ||
226 | var fieldsToParse = mergeStaticProps(this.constructor, Solr, {}, 'parsedFieldMap'); | ||
227 | var result = {}; | ||
228 | _.each(fieldsToParse, function(value, key) { | ||
229 | var parsed; | ||
230 | if (_.isFunction(value)) { | ||
231 | parsed = value.call(this, doc, index, itemHighlighting, key); | ||
232 | } else if (_.isArray(value)) { | ||
233 | parsed = []; | ||
234 | _.each(value, function(item, i) { | ||
235 | var rawValue = doc[item.name]; | ||
236 | if (rawValue) { | ||
237 | var parsedValue = item.parse.call(this, doc, index, itemHighlighting, rawValue, item.name); | ||
238 | if (parsedValue) { | ||
239 | parsed.push(parsedValue); | ||
240 | } | ||
241 | } | ||
242 | }, this); | ||
243 | } else { | ||
244 | parsed = doc[value]; | ||
245 | } | ||
246 | result[key] = parsed; | ||
247 | }, this); | ||
248 | return result; | ||
249 | }, | ||
250 | toJSON: function(options) { | ||
251 | if (!options) { | ||
252 | options = {}; | ||
253 | } | ||
254 | var facets = this.get('facets'); | ||
255 | var constructor = this.constructor; | ||
256 | var result = { | ||
257 | defType: 'edismax', | ||
258 | qf: constructor.queryField, | ||
259 | wt: 'json', | ||
260 | }; | ||
261 | var ordering = this.get('ordering'); | ||
262 | var sort = ordering.get('value'); | ||
263 | if (options.resetOrdering) { | ||
264 | sort = ordering.get('items').at(0).get('value'); | ||
265 | } | ||
266 | var currentPage = this.get('currentPage'); | ||
267 | var pageSize = this.get('pageSize'); | ||
268 | if (options.resetCurrentPage) { | ||
269 | currentPage = 1; | ||
270 | } | ||
271 | result.sort = sort; | ||
272 | result.rows = pageSize; | ||
273 | result.start = (currentPage - 1) * pageSize; | ||
274 | result.fl = mergeStaticSets(constructor, Solr, [], 'returnFields'); | ||
275 | if (this.get('options').get('highlighting')) { | ||
276 | result.hl = true; | ||
277 | result['hl.fl'] = ['content', 'title'].concat(constructor.extraHighlightFields); | ||
278 | } | ||
279 | |||
280 | if (this.get('options').get('faceting')) { | ||
281 | var facetFormData = facets.getFacetFormData(); | ||
282 | var fq = facetFormData.fq; | ||
283 | delete(facetFormData.fq); | ||
284 | result = _.extend(result, facetFormData); | ||
285 | if (fq.length) { | ||
286 | if (result.fq) { | ||
287 | result.fq = result.fq.concat(fq); | ||
288 | } else { | ||
289 | result.fq = fq; | ||
290 | } | ||
291 | } | ||
292 | } | ||
293 | |||
294 | var queryParts = []; | ||
295 | var queryFields = this.get('queryFields'); | ||
296 | _.each(queryFields.keys(), function(queryName) { | ||
297 | var subQueryModel = queryFields.get(queryName); | ||
298 | var fields = subQueryModel.get('fields'); | ||
299 | var queries = subQueryModel.get('queries'); | ||
300 | var query = subQueryModel.get('query'); | ||
301 | if (query) { | ||
302 | queries = queries.concat([query]); | ||
303 | } | ||
304 | var subQuery = []; | ||
305 | for (var i = 0; i < fields.length; i++) { | ||
306 | var fieldName = fields[i]; | ||
307 | for (var j = 0; j < queries.length; j++) { | ||
308 | // FIXME: quote the query | ||
309 | subQuery.push(fieldName + ':\'' + queries[j] + '\''); | ||
310 | } | ||
311 | } | ||
312 | if (subQuery.length) { | ||
313 | queryParts.push(subQuery.join(' OR ')); | ||
314 | } | ||
315 | }, this); | ||
316 | |||
317 | var query = queryParts.join(' AND '); | ||
318 | if (!query) { | ||
319 | query = '*:*'; | ||
320 | if (!this.get('options').get('showAll') && !result.fq) { | ||
321 | result.rows = 0; | ||
322 | } | ||
323 | } | ||
324 | var dateBoostField = constructor.dateBoostField; | ||
325 | var popularityBoostField = constructor.popularityBoostField; | ||
326 | if (result.rows !== 0 && (dateBoostField || popularityBoostField)) { | ||
327 | var boostSum = []; | ||
328 | if (dateBoostField) { | ||
329 | result.dateBoost = 'recip(ms(NOW,' + dateBoostField + '),3.16e-11,1,1)'; | ||
330 | boostSum.push('$dateBoost'); | ||
331 | } | ||
332 | if (popularityBoostField) { | ||
333 | result.popularityBoost = 'def(' + popularityBoostField + ',0)'; | ||
334 | boostSum.push('$popularityBoost'); | ||
335 | } | ||
336 | result.qq = query; | ||
337 | result.q = '{!boost b=sum(' + boostSum.join(',') + ') v=$qq defType=$defType}'; | ||
338 | } else { | ||
339 | result.q = query; | ||
340 | } | ||
341 | return result; | ||
342 | }, | ||
343 | }, { | ||
344 | cleanup: function cleanup(txt) { | ||
345 | if (txt) { | ||
346 | txt = txt.replace(/&/g, '&'); | ||
347 | txt = txt.replace(/'/g, '\''); | ||
348 | txt = txt.replace(/[^a-zA-Z0-9 -\.,:;%<>\/'"|]/g, ' '); | ||
349 | } | ||
350 | return txt; | ||
351 | }, | ||
352 | returnFields: [ | ||
353 | 'keywords', | ||
354 | 'last_modified', | ||
355 | 'path', | ||
356 | 'score', | ||
357 | 'title', | ||
358 | ], | ||
359 | parsedFieldMap: { | ||
360 | content: function(doc, index, itemHighlighting) { | ||
361 | var content = itemHighlighting.content; | ||
362 | if (content && content.constructor === Array) { | ||
363 | content = content.join(' '); | ||
364 | } | ||
365 | if (!content) { | ||
366 | content = ''; | ||
367 | } | ||
368 | return this.constructor.cleanup(content); | ||
369 | }, | ||
370 | keywords: 'keywords', | ||
371 | lastModified: 'last_modified', | ||
372 | path: 'url', | ||
373 | score: 'score', | ||
374 | title: function(doc, index, itemHighlighting) { | ||
375 | var title; | ||
376 | if (itemHighlighting.title) { | ||
377 | title = itemHighlighting.title.join(' '); | ||
378 | } else { | ||
379 | title = doc.title[0]; | ||
380 | } | ||
381 | return this.constructor.cleanup(title); | ||
382 | }, | ||
383 | }, | ||
384 | }); | ||
385 | Solr.Facets = Facets; | ||
386 | Solr.Pagination = Pagination; | ||
387 | return Solr; | ||
388 | }); |
-
Please register or sign in to post a comment