algoliasearch-rails 1.7.1 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog +5 -0
- data/Gemfile.lock +21 -18
- data/README.md +10 -0
- data/VERSION +1 -1
- data/algoliasearch-rails.gemspec +1 -1
- data/lib/algoliasearch-rails.rb +44 -19
- data/vendor/assets/javascripts/algolia/algoliasearch.js +1130 -405
- data/vendor/assets/javascripts/algolia/algoliasearch.min.js +3 -3
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9862c9e947777afdb827b6ca9c9879e989573ce3
|
|
4
|
+
data.tar.gz: bbfcbd83a12f2ecaf38ed346a80167b214303d30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ce9950505a350f386a8a778213a2352c98a0fac56fbdef99b0ba899270ed939b4bc92202e72ce16ad214fb7cad2ecbd9b816a7b811c3136ce12b263a9373a116
|
|
7
|
+
data.tar.gz: c1d747eedcec3a4fb90d9394e6f1476c72f22142e1dd2551c6661219c5a765f0472cf2c7ee583a115f0da47061eabfca4865ceb1b4be2ac5d4f9533025dfaf24
|
data/ChangeLog
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -17,10 +17,10 @@ GEM
|
|
|
17
17
|
activesupport (= 4.0.2)
|
|
18
18
|
arel (~> 4.0.0)
|
|
19
19
|
activerecord-deprecated_finders (1.0.3)
|
|
20
|
-
activerecord-jdbc-adapter (1.3.
|
|
20
|
+
activerecord-jdbc-adapter (1.3.6)
|
|
21
21
|
activerecord (>= 2.2)
|
|
22
|
-
activerecord-jdbcsqlite3-adapter (1.3.
|
|
23
|
-
activerecord-jdbc-adapter (~> 1.3.
|
|
22
|
+
activerecord-jdbcsqlite3-adapter (1.3.6)
|
|
23
|
+
activerecord-jdbc-adapter (~> 1.3.6)
|
|
24
24
|
jdbc-sqlite3 (~> 3.7.2)
|
|
25
25
|
activesupport (4.0.2)
|
|
26
26
|
i18n (~> 0.6, >= 0.6.4)
|
|
@@ -29,7 +29,7 @@ GEM
|
|
|
29
29
|
thread_safe (~> 0.1)
|
|
30
30
|
tzinfo (~> 0.3.37)
|
|
31
31
|
addressable (2.3.5)
|
|
32
|
-
algoliasearch (1.2.
|
|
32
|
+
algoliasearch (1.2.2)
|
|
33
33
|
httpclient (~> 2.3)
|
|
34
34
|
json (>= 1.5.1)
|
|
35
35
|
arel (4.0.1)
|
|
@@ -40,7 +40,7 @@ GEM
|
|
|
40
40
|
autotest-fsevent (0.2.9)
|
|
41
41
|
sys-uname
|
|
42
42
|
autotest-growl (0.2.16)
|
|
43
|
-
backports (3.
|
|
43
|
+
backports (3.5.0)
|
|
44
44
|
builder (3.1.4)
|
|
45
45
|
coderay (1.1.0)
|
|
46
46
|
diff-lcs (1.2.5)
|
|
@@ -48,14 +48,14 @@ GEM
|
|
|
48
48
|
ethon (0.6.2)
|
|
49
49
|
ffi (>= 1.3.0)
|
|
50
50
|
mime-types (~> 1.18)
|
|
51
|
-
faraday (0.8.
|
|
51
|
+
faraday (0.8.9)
|
|
52
52
|
multipart-post (~> 1.2.0)
|
|
53
53
|
faraday_middleware (0.9.0)
|
|
54
54
|
faraday (>= 0.7.4, < 0.9)
|
|
55
55
|
ffi (1.9.3)
|
|
56
56
|
ffi (1.9.3-java)
|
|
57
57
|
ffi2-generators (0.1.1)
|
|
58
|
-
gh (0.13.
|
|
58
|
+
gh (0.13.2)
|
|
59
59
|
addressable
|
|
60
60
|
backports
|
|
61
61
|
faraday (~> 0.8)
|
|
@@ -68,23 +68,26 @@ GEM
|
|
|
68
68
|
jdbc-sqlite3 (3.7.2.1)
|
|
69
69
|
json (1.8.1)
|
|
70
70
|
json (1.8.1-java)
|
|
71
|
-
kaminari (0.15.
|
|
71
|
+
kaminari (0.15.1)
|
|
72
72
|
actionpack (>= 3.0.0)
|
|
73
73
|
activesupport (>= 3.0.0)
|
|
74
74
|
launchy (2.4.2)
|
|
75
75
|
addressable (~> 2.3)
|
|
76
|
+
launchy (2.4.2-java)
|
|
77
|
+
addressable (~> 2.3)
|
|
78
|
+
spoon (~> 0.0.1)
|
|
76
79
|
method_source (0.8.2)
|
|
77
80
|
mime-types (1.25.1)
|
|
78
81
|
minitest (4.7.5)
|
|
79
|
-
multi_json (1.8.
|
|
82
|
+
multi_json (1.8.4)
|
|
80
83
|
multipart-post (1.2.0)
|
|
81
|
-
net-http-persistent (2.9)
|
|
84
|
+
net-http-persistent (2.9.1)
|
|
82
85
|
net-http-pipeline (1.0.1)
|
|
83
|
-
pry (0.9.12.
|
|
86
|
+
pry (0.9.12.6)
|
|
84
87
|
coderay (~> 1.0)
|
|
85
88
|
method_source (~> 0.8)
|
|
86
89
|
slop (~> 3.4)
|
|
87
|
-
pry (0.9.12.
|
|
90
|
+
pry (0.9.12.6-java)
|
|
88
91
|
coderay (~> 1.0)
|
|
89
92
|
method_source (~> 0.8)
|
|
90
93
|
slop (~> 3.4)
|
|
@@ -95,7 +98,7 @@ GEM
|
|
|
95
98
|
rack-test (0.6.2)
|
|
96
99
|
rack (>= 1.0)
|
|
97
100
|
rake (10.1.1)
|
|
98
|
-
rdoc (4.1.
|
|
101
|
+
rdoc (4.1.1)
|
|
99
102
|
json (~> 1.4)
|
|
100
103
|
redgreen (1.2.2)
|
|
101
104
|
rspec (2.14.1)
|
|
@@ -103,9 +106,9 @@ GEM
|
|
|
103
106
|
rspec-expectations (~> 2.14.0)
|
|
104
107
|
rspec-mocks (~> 2.14.0)
|
|
105
108
|
rspec-core (2.14.7)
|
|
106
|
-
rspec-expectations (2.14.
|
|
109
|
+
rspec-expectations (2.14.5)
|
|
107
110
|
diff-lcs (>= 1.1.3, < 2.0)
|
|
108
|
-
rspec-mocks (2.14.
|
|
111
|
+
rspec-mocks (2.14.5)
|
|
109
112
|
rubysl (2.0.15)
|
|
110
113
|
rubysl-abbrev (~> 2.0)
|
|
111
114
|
rubysl-base64 (~> 2.0)
|
|
@@ -264,7 +267,7 @@ GEM
|
|
|
264
267
|
rubysl-observer (2.0.0)
|
|
265
268
|
rubysl-open-uri (2.0.0)
|
|
266
269
|
rubysl-open3 (2.0.0)
|
|
267
|
-
rubysl-openssl (2.0
|
|
270
|
+
rubysl-openssl (2.1.0)
|
|
268
271
|
rubysl-optparse (2.0.1)
|
|
269
272
|
rubysl-shellwords (~> 2.0)
|
|
270
273
|
rubysl-ostruct (2.0.4)
|
|
@@ -279,7 +282,7 @@ GEM
|
|
|
279
282
|
rubysl-readline (2.0.2)
|
|
280
283
|
rubysl-resolv (2.0.0)
|
|
281
284
|
rubysl-rexml (2.0.2)
|
|
282
|
-
rubysl-rinda (2.0.
|
|
285
|
+
rubysl-rinda (2.0.1)
|
|
283
286
|
rubysl-rss (2.0.0)
|
|
284
287
|
rubysl-scanf (2.0.0)
|
|
285
288
|
rubysl-securerandom (2.0.0)
|
|
@@ -318,7 +321,7 @@ GEM
|
|
|
318
321
|
atomic
|
|
319
322
|
thread_safe (0.1.3-java)
|
|
320
323
|
atomic
|
|
321
|
-
travis (1.6.
|
|
324
|
+
travis (1.6.7)
|
|
322
325
|
addressable (~> 2.3)
|
|
323
326
|
backports
|
|
324
327
|
faraday (~> 0.8.7)
|
data/README.md
CHANGED
|
@@ -102,10 +102,20 @@ class Product < ActiveRecord::Base
|
|
|
102
102
|
end
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
A search returns ORM-compliant objects reloading them from your database.
|
|
106
|
+
|
|
105
107
|
```ruby
|
|
106
108
|
p Contact.search("jon doe")
|
|
107
109
|
```
|
|
108
110
|
|
|
111
|
+
If you want to retrieve the raw JSON answer from the API, without re-loading the objects from the database, you can use:
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
p Contact.raw_search("jon doe")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
By the way, we recommend the usage of our [JavaScript API Client](https://github.com/algolia/algoliasearch-client-js) to perform queries.
|
|
118
|
+
|
|
109
119
|
**Notes:** All methods injected by the ```AlgoliaSearch``` include are prefixed by ```algolia_``` and aliased to the associated short names if they aren't already defined.
|
|
110
120
|
|
|
111
121
|
```ruby
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.7.
|
|
1
|
+
1.7.2
|
data/algoliasearch-rails.gemspec
CHANGED
data/lib/algoliasearch-rails.rb
CHANGED
|
@@ -128,16 +128,19 @@ module AlgoliaSearch
|
|
|
128
128
|
alias_method :remove_from_index!, :algolia_remove_from_index! unless method_defined? :remove_from_index!
|
|
129
129
|
alias_method :clear_index!, :algolia_clear_index! unless method_defined? :clear_index!
|
|
130
130
|
alias_method :search, :algolia_search unless method_defined? :search
|
|
131
|
+
alias_method :raw_search, :algolia_raw_search unless method_defined? :raw_search
|
|
131
132
|
alias_method :index, :algolia_index unless method_defined? :index
|
|
132
133
|
alias_method :index_name, :algolia_index_name unless method_defined? :index_name
|
|
133
134
|
alias_method :must_reindex?, :algolia_must_reindex? unless method_defined? :must_reindex?
|
|
134
135
|
end
|
|
136
|
+
|
|
137
|
+
base.cattr_accessor :algolia_options, :algolia_settings, :algolia_index_settings
|
|
135
138
|
end
|
|
136
139
|
|
|
137
140
|
def algoliasearch(options = {}, &block)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
self.algolia_index_settings = IndexSettings.new(block_given? ? Proc.new : nil)
|
|
142
|
+
self.algolia_settings = algolia_index_settings.to_settings
|
|
143
|
+
self.algolia_options = { type: algolia_full_const_get(model_name.to_s), per_page: algolia_index_settings.get_setting(:hitsPerPage) || 10, page: 1 }.merge(options)
|
|
141
144
|
|
|
142
145
|
attr_accessor :highlight_result
|
|
143
146
|
|
|
@@ -167,9 +170,10 @@ module AlgoliaSearch
|
|
|
167
170
|
return if @algolia_without_auto_index_scope
|
|
168
171
|
algolia_ensure_init
|
|
169
172
|
last_task = nil
|
|
170
|
-
|
|
173
|
+
|
|
174
|
+
algolia_find_in_batches(batch_size) do |group|
|
|
171
175
|
group.select! { |o| algolia_indexable?(o) } if algolia_conditional_index?
|
|
172
|
-
objects = group.map { |o|
|
|
176
|
+
objects = group.map { |o| algolia_index_settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o) }
|
|
173
177
|
last_task = @algolia_index.save_objects(objects)
|
|
174
178
|
end
|
|
175
179
|
@algolia_index.wait_task(last_task["taskID"]) if last_task and synchronous == true
|
|
@@ -179,9 +183,9 @@ module AlgoliaSearch
|
|
|
179
183
|
return if @algolia_without_auto_index_scope || !algolia_indexable?(object)
|
|
180
184
|
algolia_ensure_init
|
|
181
185
|
if synchronous
|
|
182
|
-
@algolia_index.add_object!(
|
|
186
|
+
@algolia_index.add_object!(algolia_index_settings.get_attributes(object), algolia_object_id_of(object))
|
|
183
187
|
else
|
|
184
|
-
@algolia_index.add_object(
|
|
188
|
+
@algolia_index.add_object(algolia_index_settings.get_attributes(object), algolia_object_id_of(object))
|
|
185
189
|
end
|
|
186
190
|
end
|
|
187
191
|
|
|
@@ -201,17 +205,21 @@ module AlgoliaSearch
|
|
|
201
205
|
@algolia_index = nil
|
|
202
206
|
end
|
|
203
207
|
|
|
204
|
-
def
|
|
208
|
+
def algolia_raw_search(q, settings = {})
|
|
205
209
|
algolia_ensure_init
|
|
206
|
-
|
|
210
|
+
@algolia_index.search(q, Hash[settings.map { |k,v| [k.to_s, v.to_s] }])
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def algolia_search(q, settings = {})
|
|
214
|
+
json = algolia_raw_search(q, settings)
|
|
207
215
|
results = json['hits'].map do |hit|
|
|
208
|
-
o =
|
|
216
|
+
o = algolia_options[:type].where(algolia_object_id_method => hit['objectID']).first
|
|
209
217
|
if o
|
|
210
218
|
o.highlight_result = hit['_highlightResult']
|
|
211
219
|
o
|
|
212
220
|
end
|
|
213
221
|
end.compact
|
|
214
|
-
AlgoliaSearch::Pagination.create(results, json['nbHits'].to_i,
|
|
222
|
+
AlgoliaSearch::Pagination.create(results, json['nbHits'].to_i, algolia_options)
|
|
215
223
|
end
|
|
216
224
|
|
|
217
225
|
def algolia_index
|
|
@@ -220,14 +228,14 @@ module AlgoliaSearch
|
|
|
220
228
|
end
|
|
221
229
|
|
|
222
230
|
def algolia_index_name
|
|
223
|
-
name =
|
|
224
|
-
name = "#{name}_#{Rails.env.to_s}" if
|
|
231
|
+
name = algolia_options[:index_name] || model_name.to_s.gsub('::', '_')
|
|
232
|
+
name = "#{name}_#{Rails.env.to_s}" if algolia_options[:per_environment]
|
|
225
233
|
name
|
|
226
234
|
end
|
|
227
235
|
|
|
228
236
|
def algolia_must_reindex?(object)
|
|
229
237
|
return true if algolia_object_id_changed?(object)
|
|
230
|
-
|
|
238
|
+
algolia_index_settings.get_attributes(object).each do |k, v|
|
|
231
239
|
changed_method = "#{k}_changed?"
|
|
232
240
|
return true if object.respond_to?(changed_method) && object.send(changed_method)
|
|
233
241
|
end
|
|
@@ -240,13 +248,13 @@ module AlgoliaSearch
|
|
|
240
248
|
return if @algolia_index
|
|
241
249
|
@algolia_index = Algolia::Index.new(algolia_index_name)
|
|
242
250
|
current_settings = @algolia_index.get_settings rescue nil # if the index doesn't exist
|
|
243
|
-
@algolia_index.set_settings(
|
|
251
|
+
@algolia_index.set_settings(algolia_settings) if algolia_index_settings_changed?(current_settings, algolia_settings)
|
|
244
252
|
end
|
|
245
253
|
|
|
246
254
|
private
|
|
247
255
|
|
|
248
256
|
def algolia_object_id_method
|
|
249
|
-
|
|
257
|
+
algolia_options[:id] || algolia_options[:object_id] || :id
|
|
250
258
|
end
|
|
251
259
|
|
|
252
260
|
def algolia_object_id_of(o)
|
|
@@ -285,12 +293,12 @@ module AlgoliaSearch
|
|
|
285
293
|
end
|
|
286
294
|
|
|
287
295
|
def algolia_conditional_index?
|
|
288
|
-
|
|
296
|
+
algolia_options[:if].present? || algolia_options[:unless].present?
|
|
289
297
|
end
|
|
290
298
|
|
|
291
299
|
def algolia_indexable?(object)
|
|
292
|
-
if_passes =
|
|
293
|
-
unless_passes =
|
|
300
|
+
if_passes = algolia_options[:if].blank? || algolia_constraint_passes?(object, algolia_options[:if])
|
|
301
|
+
unless_passes = algolia_options[:unless].blank? || !algolia_constraint_passes?(object, algolia_options[:unless])
|
|
294
302
|
if_passes && unless_passes
|
|
295
303
|
end
|
|
296
304
|
|
|
@@ -311,6 +319,23 @@ module AlgoliaSearch
|
|
|
311
319
|
end
|
|
312
320
|
end
|
|
313
321
|
end
|
|
322
|
+
|
|
323
|
+
def algolia_find_in_batches(batch_size, &block)
|
|
324
|
+
if (defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)) || respond_to?(:find_in_batches)
|
|
325
|
+
find_in_batches(batch_size: batch_size, &block)
|
|
326
|
+
else
|
|
327
|
+
# don't worry, mongoid has its own underlying cursor/streaming mechanism
|
|
328
|
+
items = []
|
|
329
|
+
all.each do |item|
|
|
330
|
+
items << item
|
|
331
|
+
if items.length % batch_size == 0
|
|
332
|
+
yield items
|
|
333
|
+
items = []
|
|
334
|
+
end
|
|
335
|
+
yield items unless items.empty?
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
314
339
|
end
|
|
315
340
|
|
|
316
341
|
# these are the instance methods included
|
|
@@ -1,74 +1,216 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2013 Algolia
|
|
3
|
+
* http://www.algolia.com/
|
|
4
|
+
*
|
|
5
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
* in the Software without restriction, including without limitation the rights
|
|
8
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
* furnished to do so, subject to the following conditions:
|
|
11
|
+
*
|
|
12
|
+
* The above copyright notice and this permission notice shall be included in
|
|
13
|
+
* all copies or substantial portions of the Software.
|
|
14
|
+
*
|
|
15
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
* THE SOFTWARE.
|
|
5
22
|
*/
|
|
6
23
|
|
|
7
|
-
var
|
|
24
|
+
var ALGOLIA_VERSION = '2.3.8';
|
|
8
25
|
|
|
26
|
+
/*
|
|
27
|
+
* Copyright (c) 2013 Algolia
|
|
28
|
+
* http://www.algolia.com/
|
|
29
|
+
*
|
|
30
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
31
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
32
|
+
* in the Software without restriction, including without limitation the rights
|
|
33
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
34
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
35
|
+
* furnished to do so, subject to the following conditions:
|
|
36
|
+
*
|
|
37
|
+
* The above copyright notice and this permission notice shall be included in
|
|
38
|
+
* all copies or substantial portions of the Software.
|
|
39
|
+
*
|
|
40
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
41
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
42
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
43
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
44
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
45
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
46
|
+
* THE SOFTWARE.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
* Algolia Search library initialization
|
|
51
|
+
* @param applicationID the application ID you have in your admin interface
|
|
52
|
+
* @param apiKey a valid API key for the service
|
|
53
|
+
* @param method specify if the protocol used is http or https (http by default to make the first search query faster).
|
|
54
|
+
* You need to use https is you are doing something else than just search queries.
|
|
55
|
+
* @param resolveDNS let you disable first empty query that is launch to warmup the service
|
|
56
|
+
* @param hostsArray (optionnal) the list of hosts that you have received for the service
|
|
57
|
+
*/
|
|
9
58
|
var AlgoliaSearch = function(applicationID, apiKey, method, resolveDNS, hostsArray) {
|
|
10
59
|
this.applicationID = applicationID;
|
|
11
60
|
this.apiKey = apiKey;
|
|
61
|
+
|
|
12
62
|
if (this._isUndefined(hostsArray)) {
|
|
13
|
-
hostsArray = [
|
|
63
|
+
hostsArray = [applicationID + '-1.algolia.io',
|
|
64
|
+
applicationID + '-2.algolia.io',
|
|
65
|
+
applicationID + '-3.algolia.io'];
|
|
14
66
|
}
|
|
15
67
|
this.hosts = [];
|
|
68
|
+
// Add hosts in random order
|
|
16
69
|
for (var i = 0; i < hostsArray.length; ++i) {
|
|
17
|
-
if (Math.random() > .5) {
|
|
70
|
+
if (Math.random() > 0.5) {
|
|
18
71
|
this.hosts.reverse();
|
|
19
72
|
}
|
|
20
73
|
if (this._isUndefined(method) || method == null) {
|
|
21
|
-
this.hosts.push((
|
|
22
|
-
} else if (method ===
|
|
23
|
-
this.hosts.push(
|
|
74
|
+
this.hosts.push(('https:' == document.location.protocol ? 'https' : 'http') + '://' + hostsArray[i]);
|
|
75
|
+
} else if (method === 'https' || method === 'HTTPS') {
|
|
76
|
+
this.hosts.push('https://' + hostsArray[i]);
|
|
24
77
|
} else {
|
|
25
|
-
this.hosts.push(
|
|
78
|
+
this.hosts.push('http://' + hostsArray[i]);
|
|
26
79
|
}
|
|
27
80
|
}
|
|
28
|
-
if (Math.random() > .5) {
|
|
81
|
+
if (Math.random() > 0.5) {
|
|
29
82
|
this.hosts.reverse();
|
|
30
83
|
}
|
|
31
84
|
if (this._isUndefined(resolveDNS) || resolveDNS) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
85
|
+
// Perform a call to solve DNS (avoid to slow down the first user query)
|
|
86
|
+
this._jsonRequest({ method: 'GET',
|
|
87
|
+
url: '/1/isalive' });
|
|
36
88
|
}
|
|
37
89
|
this.extraHeaders = [];
|
|
38
90
|
};
|
|
39
91
|
|
|
92
|
+
function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) {
|
|
93
|
+
|
|
94
|
+
function _getHitAxplainationForOneAttr_recurse(obj, foundWords) {
|
|
95
|
+
if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) {
|
|
96
|
+
var match = false;
|
|
97
|
+
for (var j = 0; j < obj.matchedWords.length; ++j) {
|
|
98
|
+
var word = obj.matchedWords[j];
|
|
99
|
+
if (!(word in foundWords)) {
|
|
100
|
+
foundWords[word] = 1;
|
|
101
|
+
match = true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return match ? [obj.value] : [];
|
|
105
|
+
} else if (obj instanceof Array) {
|
|
106
|
+
var res = [];
|
|
107
|
+
for (var i = 0; i < obj.length; ++i) {
|
|
108
|
+
var array = _getHitAxplainationForOneAttr_recurse(obj[i], foundWords);
|
|
109
|
+
res = res.concat(array);
|
|
110
|
+
}
|
|
111
|
+
return res;
|
|
112
|
+
} else if (typeof obj === 'object') {
|
|
113
|
+
var res = [];
|
|
114
|
+
for (prop in obj) {
|
|
115
|
+
if (obj.hasOwnProperty(prop)){
|
|
116
|
+
res = res.concat(_getHitAxplainationForOneAttr_recurse(obj[prop], foundWords));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return res;
|
|
120
|
+
}
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function _getHitAxplainationForOneAttr(hit, foundWords, attr) {
|
|
125
|
+
if (attr.indexOf('.') === -1) {
|
|
126
|
+
if (attr in hit._highlightResult) {
|
|
127
|
+
return _getHitAxplainationForOneAttr_recurse(hit._highlightResult[attr], foundWords);
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
var array = attr.split('.');
|
|
132
|
+
var obj = hit._highlightResult;
|
|
133
|
+
for (var i = 0; i < array.length; ++i) {
|
|
134
|
+
if (array[i] in obj) {
|
|
135
|
+
obj = obj[array[i]];
|
|
136
|
+
} else {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return _getHitAxplainationForOneAttr_recurse(obj, foundWords);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
var res = {};
|
|
144
|
+
var foundWords = {};
|
|
145
|
+
var title = _getHitAxplainationForOneAttr(hit, foundWords, titleAttribute);
|
|
146
|
+
res.title = (title.length > 0) ? title[0] : "";
|
|
147
|
+
res.subtitles = [];
|
|
148
|
+
|
|
149
|
+
if (typeof otherAttributes !== 'undefined') {
|
|
150
|
+
for (var i = 0; i < otherAttributes.length; ++i) {
|
|
151
|
+
var attr = _getHitAxplainationForOneAttr(hit, foundWords, otherAttributes[i]);
|
|
152
|
+
for (var j = 0; j < attr.length; ++j) {
|
|
153
|
+
res.subtitles.push({ attr: otherAttributes[i], value: attr[j] });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return res;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
40
161
|
AlgoliaSearch.prototype = {
|
|
162
|
+
/*
|
|
163
|
+
* Delete an index
|
|
164
|
+
*
|
|
165
|
+
* @param indexName the name of index to delete
|
|
166
|
+
* @param callback the result callback with two arguments
|
|
167
|
+
* success: boolean set to true if the request was successfull
|
|
168
|
+
* content: the server answer that contains the task ID
|
|
169
|
+
*/
|
|
41
170
|
deleteIndex: function(indexName, callback) {
|
|
42
|
-
this._jsonRequest({
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
callback: callback
|
|
46
|
-
});
|
|
171
|
+
this._jsonRequest({ method: 'DELETE',
|
|
172
|
+
url: '/1/indexes/' + encodeURIComponent(indexName),
|
|
173
|
+
callback: callback });
|
|
47
174
|
},
|
|
175
|
+
/**
|
|
176
|
+
* Move an existing index.
|
|
177
|
+
* @param srcIndexName the name of index to copy.
|
|
178
|
+
* @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
|
|
179
|
+
* @param callback the result callback with two arguments
|
|
180
|
+
* success: boolean set to true if the request was successfull
|
|
181
|
+
* content: the server answer that contains the task ID
|
|
182
|
+
*/
|
|
48
183
|
moveIndex: function(srcIndexName, dstIndexName, callback) {
|
|
49
|
-
var postObj = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
url: "/1/indexes/" + encodeURIComponent(srcIndexName) + "/operation",
|
|
56
|
-
body: postObj,
|
|
57
|
-
callback: callback
|
|
58
|
-
});
|
|
184
|
+
var postObj = {operation: 'move', destination: dstIndexName};
|
|
185
|
+
this._jsonRequest({ method: 'POST',
|
|
186
|
+
url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
|
|
187
|
+
body: postObj,
|
|
188
|
+
callback: callback });
|
|
189
|
+
|
|
59
190
|
},
|
|
191
|
+
/**
|
|
192
|
+
* Copy an existing index.
|
|
193
|
+
* @param srcIndexName the name of index to copy.
|
|
194
|
+
* @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
|
|
195
|
+
* @param callback the result callback with two arguments
|
|
196
|
+
* success: boolean set to true if the request was successfull
|
|
197
|
+
* content: the server answer that contains the task ID
|
|
198
|
+
*/
|
|
60
199
|
copyIndex: function(srcIndexName, dstIndexName, callback) {
|
|
61
|
-
var postObj = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
method: "POST",
|
|
67
|
-
url: "/1/indexes/" + encodeURIComponent(srcIndexName) + "/operation",
|
|
68
|
-
body: postObj,
|
|
69
|
-
callback: callback
|
|
70
|
-
});
|
|
200
|
+
var postObj = {operation: 'copy', destination: dstIndexName};
|
|
201
|
+
this._jsonRequest({ method: 'POST',
|
|
202
|
+
url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
|
|
203
|
+
body: postObj,
|
|
204
|
+
callback: callback });
|
|
71
205
|
},
|
|
206
|
+
/**
|
|
207
|
+
* Return last log entries.
|
|
208
|
+
* @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
|
|
209
|
+
* @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000.
|
|
210
|
+
* @param callback the result callback with two arguments
|
|
211
|
+
* success: boolean set to true if the request was successfull
|
|
212
|
+
* content: the server answer that contains the task ID
|
|
213
|
+
*/
|
|
72
214
|
getLogs: function(callback, offset, length) {
|
|
73
215
|
if (this._isUndefined(offset)) {
|
|
74
216
|
offset = 0;
|
|
@@ -76,53 +218,110 @@ AlgoliaSearch.prototype = {
|
|
|
76
218
|
if (this._isUndefined(length)) {
|
|
77
219
|
length = 10;
|
|
78
220
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
221
|
+
|
|
222
|
+
this._jsonRequest({ method: 'GET',
|
|
223
|
+
url: '/1/logs?offset=' + offset + '&length=' + length,
|
|
224
|
+
callback: callback });
|
|
84
225
|
},
|
|
226
|
+
/*
|
|
227
|
+
* List all existing indexes
|
|
228
|
+
*
|
|
229
|
+
* @param callback the result callback with two arguments
|
|
230
|
+
* success: boolean set to true if the request was successfull
|
|
231
|
+
* content: the server answer with index list or error description if success is false.
|
|
232
|
+
*/
|
|
85
233
|
listIndexes: function(callback) {
|
|
86
|
-
this._jsonRequest({
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
callback: callback
|
|
90
|
-
});
|
|
234
|
+
this._jsonRequest({ method: 'GET',
|
|
235
|
+
url: '/1/indexes/',
|
|
236
|
+
callback: callback });
|
|
91
237
|
},
|
|
238
|
+
|
|
239
|
+
/*
|
|
240
|
+
* Get the index object initialized
|
|
241
|
+
*
|
|
242
|
+
* @param indexName the name of index
|
|
243
|
+
* @param callback the result callback with one argument (the Index instance)
|
|
244
|
+
*/
|
|
92
245
|
initIndex: function(indexName) {
|
|
93
246
|
return new this.Index(this, indexName);
|
|
94
247
|
},
|
|
248
|
+
/*
|
|
249
|
+
* List all existing user keys with their associated ACLs
|
|
250
|
+
*
|
|
251
|
+
* @param callback the result callback with two arguments
|
|
252
|
+
* success: boolean set to true if the request was successfull
|
|
253
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
254
|
+
*/
|
|
95
255
|
listUserKeys: function(callback) {
|
|
96
|
-
this._jsonRequest({
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
callback: callback
|
|
100
|
-
});
|
|
256
|
+
this._jsonRequest({ method: 'GET',
|
|
257
|
+
url: '/1/keys',
|
|
258
|
+
callback: callback });
|
|
101
259
|
},
|
|
260
|
+
/*
|
|
261
|
+
* Get ACL of a user key
|
|
262
|
+
*
|
|
263
|
+
* @param callback the result callback with two arguments
|
|
264
|
+
* success: boolean set to true if the request was successfull
|
|
265
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
266
|
+
*/
|
|
102
267
|
getUserKeyACL: function(key, callback) {
|
|
103
|
-
this._jsonRequest({
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
callback: callback
|
|
107
|
-
});
|
|
268
|
+
this._jsonRequest({ method: 'GET',
|
|
269
|
+
url: '/1/keys/' + key,
|
|
270
|
+
callback: callback });
|
|
108
271
|
},
|
|
272
|
+
/*
|
|
273
|
+
* Delete an existing user key
|
|
274
|
+
*
|
|
275
|
+
* @param callback the result callback with two arguments
|
|
276
|
+
* success: boolean set to true if the request was successfull
|
|
277
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
278
|
+
*/
|
|
109
279
|
deleteUserKey: function(key, callback) {
|
|
110
|
-
this._jsonRequest({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
callback: callback
|
|
114
|
-
});
|
|
280
|
+
this._jsonRequest({ method: 'DELETE',
|
|
281
|
+
url: '/1/keys/' + key,
|
|
282
|
+
callback: callback });
|
|
115
283
|
},
|
|
284
|
+
/*
|
|
285
|
+
* Add an existing user key
|
|
286
|
+
*
|
|
287
|
+
* @param acls the list of ACL for this key. Defined by an array of strings that
|
|
288
|
+
* can contains the following values:
|
|
289
|
+
* - search: allow to search (https and http)
|
|
290
|
+
* - addObject: allows to add/update an object in the index (https only)
|
|
291
|
+
* - deleteObject : allows to delete an existing object (https only)
|
|
292
|
+
* - deleteIndex : allows to delete index content (https only)
|
|
293
|
+
* - settings : allows to get index settings (https only)
|
|
294
|
+
* - editSettings : allows to change index settings (https only)
|
|
295
|
+
* @param callback the result callback with two arguments
|
|
296
|
+
* success: boolean set to true if the request was successfull
|
|
297
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
298
|
+
*/
|
|
116
299
|
addUserKey: function(acls, callback) {
|
|
117
300
|
var aclsObject = {};
|
|
118
301
|
aclsObject.acl = acls;
|
|
119
|
-
this._jsonRequest({
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
callback: callback
|
|
124
|
-
});
|
|
302
|
+
this._jsonRequest({ method: 'POST',
|
|
303
|
+
url: '/1/keys',
|
|
304
|
+
body: aclsObject,
|
|
305
|
+
callback: callback });
|
|
125
306
|
},
|
|
307
|
+
/*
|
|
308
|
+
* Add an existing user key
|
|
309
|
+
*
|
|
310
|
+
* @param acls the list of ACL for this key. Defined by an array of strings that
|
|
311
|
+
* can contains the following values:
|
|
312
|
+
* - search: allow to search (https and http)
|
|
313
|
+
* - addObject: allows to add/update an object in the index (https only)
|
|
314
|
+
* - deleteObject : allows to delete an existing object (https only)
|
|
315
|
+
* - deleteIndex : allows to delete index content (https only)
|
|
316
|
+
* - settings : allows to get index settings (https only)
|
|
317
|
+
* - editSettings : allows to change index settings (https only)
|
|
318
|
+
* @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
|
|
319
|
+
* @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
|
|
320
|
+
* @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
|
|
321
|
+
* @param callback the result callback with two arguments
|
|
322
|
+
* success: boolean set to true if the request was successfull
|
|
323
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
324
|
+
*/
|
|
126
325
|
addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
|
|
127
326
|
var indexObj = this;
|
|
128
327
|
var aclsObject = {};
|
|
@@ -130,42 +329,64 @@ AlgoliaSearch.prototype = {
|
|
|
130
329
|
aclsObject.validity = validity;
|
|
131
330
|
aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
|
|
132
331
|
aclsObject.maxHitsPerQuery = maxHitsPerQuery;
|
|
133
|
-
this._jsonRequest({
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
callback: callback
|
|
138
|
-
});
|
|
332
|
+
this._jsonRequest({ method: 'POST',
|
|
333
|
+
url: '/1/indexes/' + indexObj.indexName + '/keys',
|
|
334
|
+
body: aclsObject,
|
|
335
|
+
callback: callback });
|
|
139
336
|
},
|
|
337
|
+
/*
|
|
338
|
+
* Initialize a new batch of search queries
|
|
339
|
+
*/
|
|
140
340
|
startQueriesBatch: function() {
|
|
141
341
|
this.batch = [];
|
|
142
342
|
},
|
|
343
|
+
/*
|
|
344
|
+
* Add a search query in the batch
|
|
345
|
+
*
|
|
346
|
+
* @param query the full text query
|
|
347
|
+
* @param args (optional) if set, contains an object with query parameters:
|
|
348
|
+
* - attributes: an array of object attribute names to retrieve
|
|
349
|
+
* (if not set all attributes are retrieve)
|
|
350
|
+
* - attributesToHighlight: an array of object attribute names to highlight
|
|
351
|
+
* (if not set indexed attributes are highlighted)
|
|
352
|
+
* - minWordSizefor1Typo: the minimum number of characters to accept one typo.
|
|
353
|
+
* Defaults to 3.
|
|
354
|
+
* - minWordSizefor2Typos: the minimum number of characters to accept two typos.
|
|
355
|
+
* Defaults to 7.
|
|
356
|
+
* - getRankingInfo: if set, the result hits will contain ranking information in
|
|
357
|
+
* _rankingInfo attribute
|
|
358
|
+
* - page: (pagination parameter) page to retrieve (zero base). Defaults to 0.
|
|
359
|
+
* - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10.
|
|
360
|
+
*/
|
|
143
361
|
addQueryInBatch: function(indexName, query, args) {
|
|
144
|
-
var params =
|
|
362
|
+
var params = 'query=' + encodeURIComponent(query);
|
|
145
363
|
if (!this._isUndefined(args) && args != null) {
|
|
146
364
|
params = this._getSearchParams(args, params);
|
|
147
365
|
}
|
|
148
|
-
this.batch.push({
|
|
149
|
-
indexName: indexName,
|
|
150
|
-
params: params
|
|
151
|
-
});
|
|
366
|
+
this.batch.push({ indexName: indexName, params: params });
|
|
152
367
|
},
|
|
368
|
+
/*
|
|
369
|
+
* Clear all queries in cache
|
|
370
|
+
*/
|
|
153
371
|
clearCache: function() {
|
|
154
372
|
this.cache = {};
|
|
155
373
|
},
|
|
374
|
+
/*
|
|
375
|
+
* Launch the batch of queries using XMLHttpRequest.
|
|
376
|
+
* (Optimized for browser using a POST query to minimize number of OPTIONS queries)
|
|
377
|
+
*
|
|
378
|
+
* @param callback the function that will receive results
|
|
379
|
+
* @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime.
|
|
380
|
+
*/
|
|
156
381
|
sendQueriesBatch: function(callback, delay) {
|
|
157
382
|
var as = this;
|
|
158
|
-
var params = {
|
|
159
|
-
requests: [],
|
|
160
|
-
apiKey: this.apiKey,
|
|
161
|
-
appID: this.applicationID
|
|
162
|
-
};
|
|
383
|
+
var params = {requests: [], apiKey: this.apiKey, appID: this.applicationID};
|
|
163
384
|
for (var i = 0; i < as.batch.length; ++i) {
|
|
164
385
|
params.requests.push(as.batch[i]);
|
|
165
386
|
}
|
|
166
387
|
window.clearTimeout(as.onDelayTrigger);
|
|
167
388
|
if (!this._isUndefined(delay) && delay != null && delay > 0) {
|
|
168
|
-
var onDelayTrigger = window.setTimeout(function() {
|
|
389
|
+
var onDelayTrigger = window.setTimeout( function() {
|
|
169
390
|
as._sendQueriesBatch(params, callback);
|
|
170
391
|
}, delay);
|
|
171
392
|
as.onDelayTrigger = onDelayTrigger;
|
|
@@ -173,34 +394,38 @@ AlgoliaSearch.prototype = {
|
|
|
173
394
|
this._sendQueriesBatch(params, callback);
|
|
174
395
|
}
|
|
175
396
|
},
|
|
397
|
+
/*
|
|
398
|
+
* Index class constructor.
|
|
399
|
+
* You should not use this method directly but use initIndex() function
|
|
400
|
+
*/
|
|
176
401
|
Index: function(algoliasearch, indexName) {
|
|
177
402
|
this.indexName = indexName;
|
|
178
403
|
this.as = algoliasearch;
|
|
179
404
|
this.typeAheadArgs = null;
|
|
180
405
|
this.typeAheadValueOption = null;
|
|
181
406
|
},
|
|
407
|
+
|
|
182
408
|
setExtraHeader: function(key, value) {
|
|
183
|
-
this.extraHeaders.push({
|
|
184
|
-
key: key,
|
|
185
|
-
value: value
|
|
186
|
-
});
|
|
409
|
+
this.extraHeaders.push({ key: key, value: value});
|
|
187
410
|
},
|
|
411
|
+
|
|
188
412
|
_sendQueriesBatch: function(params, callback) {
|
|
189
|
-
this._jsonRequest({
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
callback: callback
|
|
195
|
-
});
|
|
413
|
+
this._jsonRequest({ cache: this.cache,
|
|
414
|
+
method: 'POST',
|
|
415
|
+
url: '/1/indexes/*/queries',
|
|
416
|
+
body: params,
|
|
417
|
+
callback: callback });
|
|
196
418
|
},
|
|
419
|
+
/*
|
|
420
|
+
* Wrapper that try all hosts to maximize the quality of service
|
|
421
|
+
*/
|
|
197
422
|
_jsonRequest: function(opts) {
|
|
198
423
|
var self = this;
|
|
199
424
|
var callback = opts.callback;
|
|
200
425
|
var cache = null;
|
|
201
426
|
var cacheID = opts.url;
|
|
202
427
|
if (!this._isUndefined(opts.body)) {
|
|
203
|
-
cacheID = opts.url +
|
|
428
|
+
cacheID = opts.url + '_body_' + JSON.stringify(opts.body);
|
|
204
429
|
}
|
|
205
430
|
if (!this._isUndefined(opts.cache)) {
|
|
206
431
|
cache = opts.cache;
|
|
@@ -211,6 +436,7 @@ AlgoliaSearch.prototype = {
|
|
|
211
436
|
return;
|
|
212
437
|
}
|
|
213
438
|
}
|
|
439
|
+
|
|
214
440
|
var impl = function(position) {
|
|
215
441
|
var idx = 0;
|
|
216
442
|
if (!self._isUndefined(position)) {
|
|
@@ -218,20 +444,18 @@ AlgoliaSearch.prototype = {
|
|
|
218
444
|
}
|
|
219
445
|
if (self.hosts.length <= idx) {
|
|
220
446
|
if (!self._isUndefined(callback)) {
|
|
221
|
-
callback(false, {
|
|
222
|
-
message: "Cannot contact server"
|
|
223
|
-
});
|
|
447
|
+
callback(false, { message: 'Cannot contact server'});
|
|
224
448
|
}
|
|
225
449
|
return;
|
|
226
450
|
}
|
|
227
451
|
opts.callback = function(retry, success, res, body) {
|
|
228
452
|
if (!success && !self._isUndefined(body)) {
|
|
229
|
-
console.log(
|
|
453
|
+
console.log('Error: ' + body.message);
|
|
230
454
|
}
|
|
231
455
|
if (success && !self._isUndefined(opts.cache)) {
|
|
232
456
|
cache[cacheID] = body;
|
|
233
457
|
}
|
|
234
|
-
if (!success && retry && idx + 1 < self.hosts.length) {
|
|
458
|
+
if (!success && retry && (idx + 1) < self.hosts.length) {
|
|
235
459
|
impl(idx + 1);
|
|
236
460
|
} else {
|
|
237
461
|
if (!self._isUndefined(callback)) {
|
|
@@ -244,6 +468,7 @@ AlgoliaSearch.prototype = {
|
|
|
244
468
|
};
|
|
245
469
|
impl();
|
|
246
470
|
},
|
|
471
|
+
|
|
247
472
|
_jsonRequestByHost: function(opts) {
|
|
248
473
|
var body = null;
|
|
249
474
|
var self = this;
|
|
@@ -252,47 +477,53 @@ AlgoliaSearch.prototype = {
|
|
|
252
477
|
}
|
|
253
478
|
var url = opts.hostname + opts.url;
|
|
254
479
|
var xmlHttp = null;
|
|
480
|
+
|
|
255
481
|
xmlHttp = new XMLHttpRequest();
|
|
256
|
-
if (
|
|
257
|
-
xmlHttp.open(opts.method, url, true);
|
|
258
|
-
xmlHttp.setRequestHeader(
|
|
259
|
-
xmlHttp.setRequestHeader(
|
|
482
|
+
if ('withCredentials' in xmlHttp) {
|
|
483
|
+
xmlHttp.open(opts.method, url , true);
|
|
484
|
+
xmlHttp.setRequestHeader('X-Algolia-API-Key', this.apiKey);
|
|
485
|
+
xmlHttp.setRequestHeader('X-Algolia-Application-Id', this.applicationID);
|
|
260
486
|
for (var i = 0; i < this.extraHeaders.length; ++i) {
|
|
261
487
|
xmlHttp.setRequestHeader(this.extraHeaders[i].key, this.extraHeaders[i].value);
|
|
262
488
|
}
|
|
263
489
|
if (body != null) {
|
|
264
|
-
xmlHttp.setRequestHeader(
|
|
490
|
+
xmlHttp.setRequestHeader('Content-type', 'application/json');
|
|
265
491
|
}
|
|
266
|
-
} else if (typeof XDomainRequest !=
|
|
492
|
+
} else if (typeof XDomainRequest != 'undefined') {
|
|
493
|
+
// Handle IE8/IE9
|
|
494
|
+
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
|
|
267
495
|
xmlHttp = new XDomainRequest();
|
|
268
496
|
xmlHttp.open(opts.method, url);
|
|
269
497
|
} else {
|
|
270
|
-
|
|
498
|
+
// very old browser, not supported
|
|
499
|
+
console.log('your browser is too old to support CORS requests');
|
|
271
500
|
}
|
|
272
501
|
xmlHttp.send(body);
|
|
273
502
|
xmlHttp.onload = function(event) {
|
|
274
503
|
if (!self._isUndefined(event) && event.target != null) {
|
|
275
|
-
var retry = event.target.status === 0 || event.target.status === 503;
|
|
276
|
-
var success = event.target.status === 200 || event.target.status === 201;
|
|
504
|
+
var retry = (event.target.status === 0 || event.target.status === 503);
|
|
505
|
+
var success = (event.target.status === 200 || event.target.status === 201);
|
|
277
506
|
opts.callback(retry, success, event.target, event.target.response != null ? JSON.parse(event.target.response) : null);
|
|
278
507
|
} else {
|
|
279
508
|
opts.callback(false, true, event, JSON.parse(xmlHttp.responseText));
|
|
280
509
|
}
|
|
281
510
|
};
|
|
282
511
|
xmlHttp.onerror = function() {
|
|
283
|
-
opts.callback(true, false, null, {
|
|
284
|
-
message: "Could not connect to Host"
|
|
285
|
-
});
|
|
512
|
+
opts.callback(true, false, null, { 'message': 'Could not connect to Host'} );
|
|
286
513
|
};
|
|
287
514
|
},
|
|
515
|
+
|
|
516
|
+
/*
|
|
517
|
+
* Transform search param object in query string
|
|
518
|
+
*/
|
|
288
519
|
_getSearchParams: function(args, params) {
|
|
289
520
|
if (this._isUndefined(args) || args == null) {
|
|
290
521
|
return params;
|
|
291
522
|
}
|
|
292
523
|
for (var key in args) {
|
|
293
524
|
if (key != null && args.hasOwnProperty(key)) {
|
|
294
|
-
params += params.length === 0 ?
|
|
295
|
-
params += key +
|
|
525
|
+
params += (params.length === 0) ? '?' : '&';
|
|
526
|
+
params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]);
|
|
296
527
|
}
|
|
297
528
|
}
|
|
298
529
|
return params;
|
|
@@ -300,6 +531,8 @@ AlgoliaSearch.prototype = {
|
|
|
300
531
|
_isUndefined: function(obj) {
|
|
301
532
|
return obj === void 0;
|
|
302
533
|
},
|
|
534
|
+
|
|
535
|
+
/// internal attributes
|
|
303
536
|
applicationID: null,
|
|
304
537
|
apiKey: null,
|
|
305
538
|
hosts: [],
|
|
@@ -307,311 +540,803 @@ AlgoliaSearch.prototype = {
|
|
|
307
540
|
extraHeaders: []
|
|
308
541
|
};
|
|
309
542
|
|
|
543
|
+
/*
|
|
544
|
+
* Contains all the functions related to one index
|
|
545
|
+
* You should use AlgoliaSearch.initIndex(indexName) to retrieve this object
|
|
546
|
+
*/
|
|
310
547
|
AlgoliaSearch.prototype.Index.prototype = {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
548
|
+
/*
|
|
549
|
+
* Clear all queries in cache
|
|
550
|
+
*/
|
|
551
|
+
clearCache: function() {
|
|
552
|
+
this.cache = {};
|
|
553
|
+
},
|
|
554
|
+
/*
|
|
555
|
+
* Add an object in this index
|
|
556
|
+
*
|
|
557
|
+
* @param content contains the javascript object to add inside the index
|
|
558
|
+
* @param callback (optional) the result callback with two arguments:
|
|
559
|
+
* success: boolean set to true if the request was successfull
|
|
560
|
+
* content: the server answer that contains 3 elements: createAt, taskId and objectID
|
|
561
|
+
* @param objectID (optional) an objectID you want to attribute to this object
|
|
562
|
+
* (if the attribute already exist the old object will be overwrite)
|
|
563
|
+
*/
|
|
564
|
+
addObject: function(content, callback, objectID) {
|
|
565
|
+
var indexObj = this;
|
|
566
|
+
if (this.as._isUndefined(objectID)) {
|
|
567
|
+
this.as._jsonRequest({ method: 'POST',
|
|
568
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName),
|
|
569
|
+
body: content,
|
|
570
|
+
callback: callback });
|
|
571
|
+
} else {
|
|
572
|
+
this.as._jsonRequest({ method: 'PUT',
|
|
573
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
|
|
574
|
+
body: content,
|
|
575
|
+
callback: callback });
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
},
|
|
579
|
+
/*
|
|
580
|
+
* Add several objects
|
|
581
|
+
*
|
|
582
|
+
* @param objects contains an array of objects to add
|
|
583
|
+
* @param callback (optional) the result callback with two arguments:
|
|
584
|
+
* success: boolean set to true if the request was successfull
|
|
585
|
+
* content: the server answer that updateAt and taskID
|
|
586
|
+
*/
|
|
587
|
+
addObjects: function(objects, callback) {
|
|
588
|
+
var indexObj = this;
|
|
589
|
+
var postObj = {requests:[]};
|
|
590
|
+
for (var i = 0; i < objects.length; ++i) {
|
|
591
|
+
var request = { action: 'addObject',
|
|
592
|
+
body: objects[i] };
|
|
593
|
+
postObj.requests.push(request);
|
|
594
|
+
}
|
|
595
|
+
this.as._jsonRequest({ method: 'POST',
|
|
596
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
|
|
597
|
+
body: postObj,
|
|
598
|
+
callback: callback });
|
|
599
|
+
},
|
|
600
|
+
/*
|
|
601
|
+
* Get an object from this index
|
|
602
|
+
*
|
|
603
|
+
* @param objectID the unique identifier of the object to retrieve
|
|
604
|
+
* @param callback (optional) the result callback with two arguments
|
|
605
|
+
* success: boolean set to true if the request was successfull
|
|
606
|
+
* content: the object to retrieve or the error message if a failure occured
|
|
607
|
+
* @param attributes (optional) if set, contains the array of attribute names to retrieve
|
|
608
|
+
*/
|
|
609
|
+
getObject: function(objectID, callback, attributes) {
|
|
610
|
+
var indexObj = this;
|
|
611
|
+
var params = '';
|
|
612
|
+
if (!this.as._isUndefined(attributes)) {
|
|
613
|
+
params = '?attributes=';
|
|
614
|
+
for (var i = 0; i < attributes.length; ++i) {
|
|
615
|
+
if (i !== 0) {
|
|
616
|
+
params += ',';
|
|
617
|
+
}
|
|
618
|
+
params += attributes[i];
|
|
359
619
|
}
|
|
360
|
-
params += attributes[i];
|
|
361
620
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
621
|
+
this.as._jsonRequest({ method: 'GET',
|
|
622
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params,
|
|
623
|
+
callback: callback });
|
|
624
|
+
},
|
|
625
|
+
|
|
626
|
+
/*
|
|
627
|
+
* Update partially an object (only update attributes passed in argument)
|
|
628
|
+
*
|
|
629
|
+
* @param partialObject contains the javascript attributes to override, the
|
|
630
|
+
* object must contains an objectID attribute
|
|
631
|
+
* @param callback (optional) the result callback with two arguments:
|
|
632
|
+
* success: boolean set to true if the request was successfull
|
|
633
|
+
* content: the server answer that contains 3 elements: createAt, taskId and objectID
|
|
634
|
+
*/
|
|
635
|
+
partialUpdateObject: function(partialObject, callback) {
|
|
636
|
+
var indexObj = this;
|
|
637
|
+
this.as._jsonRequest({ method: 'POST',
|
|
638
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial',
|
|
639
|
+
body: partialObject,
|
|
640
|
+
callback: callback });
|
|
641
|
+
},
|
|
642
|
+
/*
|
|
643
|
+
* Partially Override the content of several objects
|
|
644
|
+
*
|
|
645
|
+
* @param objects contains an array of objects to update (each object must contains a objectID attribute)
|
|
646
|
+
* @param callback (optional) the result callback with two arguments:
|
|
647
|
+
* success: boolean set to true if the request was successfull
|
|
648
|
+
* content: the server answer that updateAt and taskID
|
|
649
|
+
*/
|
|
650
|
+
partialUpdateObjects: function(objects, callback) {
|
|
651
|
+
var indexObj = this;
|
|
652
|
+
var postObj = {requests:[]};
|
|
653
|
+
for (var i = 0; i < objects.length; ++i) {
|
|
654
|
+
var request = { action: 'partialUpdateObject',
|
|
655
|
+
objectID: objects[i].objectID,
|
|
656
|
+
body: objects[i] };
|
|
657
|
+
postObj.requests.push(request);
|
|
658
|
+
}
|
|
659
|
+
this.as._jsonRequest({ method: 'POST',
|
|
660
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
|
|
661
|
+
body: postObj,
|
|
662
|
+
callback: callback });
|
|
663
|
+
},
|
|
664
|
+
/*
|
|
665
|
+
* Override the content of object
|
|
666
|
+
*
|
|
667
|
+
* @param object contains the javascript object to save, the object must contains an objectID attribute
|
|
668
|
+
* @param callback (optional) the result callback with two arguments:
|
|
669
|
+
* success: boolean set to true if the request was successfull
|
|
670
|
+
* content: the server answer that updateAt and taskID
|
|
671
|
+
*/
|
|
672
|
+
saveObject: function(object, callback) {
|
|
673
|
+
var indexObj = this;
|
|
674
|
+
this.as._jsonRequest({ method: 'PUT',
|
|
675
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID),
|
|
676
|
+
body: object,
|
|
677
|
+
callback: callback });
|
|
678
|
+
},
|
|
679
|
+
/*
|
|
680
|
+
* Override the content of several objects
|
|
681
|
+
*
|
|
682
|
+
* @param objects contains an array of objects to update (each object must contains a objectID attribute)
|
|
683
|
+
* @param callback (optional) the result callback with two arguments:
|
|
684
|
+
* success: boolean set to true if the request was successfull
|
|
685
|
+
* content: the server answer that updateAt and taskID
|
|
686
|
+
*/
|
|
687
|
+
saveObjects: function(objects, callback) {
|
|
688
|
+
var indexObj = this;
|
|
689
|
+
var postObj = {requests:[]};
|
|
690
|
+
for (var i = 0; i < objects.length; ++i) {
|
|
691
|
+
var request = { action: 'updateObject',
|
|
692
|
+
objectID: objects[i].objectID,
|
|
693
|
+
body: objects[i] };
|
|
694
|
+
postObj.requests.push(request);
|
|
695
|
+
}
|
|
696
|
+
this.as._jsonRequest({ method: 'POST',
|
|
697
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
|
|
698
|
+
body: postObj,
|
|
699
|
+
callback: callback });
|
|
700
|
+
},
|
|
701
|
+
/*
|
|
702
|
+
* Delete an object from the index
|
|
703
|
+
*
|
|
704
|
+
* @param objectID the unique identifier of object to delete
|
|
705
|
+
* @param callback (optional) the result callback with two arguments:
|
|
706
|
+
* success: boolean set to true if the request was successfull
|
|
707
|
+
* content: the server answer that contains 3 elements: createAt, taskId and objectID
|
|
708
|
+
*/
|
|
709
|
+
deleteObject: function(objectID, callback) {
|
|
710
|
+
if (objectID == null || objectID.length === 0) {
|
|
711
|
+
callback(false, { message: 'empty objectID'});
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
var indexObj = this;
|
|
715
|
+
this.as._jsonRequest({ method: 'DELETE',
|
|
716
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
|
|
717
|
+
callback: callback });
|
|
718
|
+
},
|
|
719
|
+
/*
|
|
720
|
+
* Search inside the index using XMLHttpRequest request (Using a POST query to
|
|
721
|
+
* minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
|
|
722
|
+
*
|
|
723
|
+
* @param query the full text query
|
|
724
|
+
* @param callback the result callback with two arguments:
|
|
725
|
+
* success: boolean set to true if the request was successfull. If false, the content contains the error.
|
|
726
|
+
* content: the server answer that contains the list of results.
|
|
727
|
+
* @param args (optional) if set, contains an object with query parameters:
|
|
728
|
+
* - page: (integer) Pagination parameter used to select the page to retrieve.
|
|
729
|
+
* Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
|
|
730
|
+
* - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20.
|
|
731
|
+
* - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size).
|
|
732
|
+
* Attributes are separated with a comma (for example "name,address").
|
|
733
|
+
* You can also use a string array encoding (for example ["name","address"]).
|
|
734
|
+
* By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index.
|
|
735
|
+
* - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query.
|
|
736
|
+
* Attributes are separated by a comma. You can also use a string array encoding (for example ["name","address"]).
|
|
737
|
+
* If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted.
|
|
738
|
+
* You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted.
|
|
739
|
+
* A matchLevel is returned for each highlighted attribute and can contain:
|
|
740
|
+
* - full: if all the query terms were found in the attribute,
|
|
741
|
+
* - partial: if only some of the query terms were found,
|
|
742
|
+
* - none: if none of the query terms were found.
|
|
743
|
+
* - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`).
|
|
744
|
+
* Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10).
|
|
745
|
+
* You can also use a string array encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is computed.
|
|
746
|
+
* - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3.
|
|
747
|
+
* - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7.
|
|
748
|
+
* - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute.
|
|
749
|
+
* - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma).
|
|
750
|
+
* For example aroundLatLng=47.316669,5.016670).
|
|
751
|
+
* You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision
|
|
752
|
+
* (for example if you set aroundPrecision=100, two objects that are distant of less than 100m will be considered as identical for "geo" ranking parameter).
|
|
753
|
+
* At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
|
|
754
|
+
* - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng).
|
|
755
|
+
* For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201).
|
|
756
|
+
* At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
|
|
757
|
+
* - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma.
|
|
758
|
+
* The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`.
|
|
759
|
+
* You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000.
|
|
760
|
+
* You can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]).
|
|
761
|
+
* - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas.
|
|
762
|
+
* To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3).
|
|
763
|
+
* You can also use a string array encoding, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3).
|
|
764
|
+
* At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}).
|
|
765
|
+
* - facetFilters: filter the query by a list of facets.
|
|
766
|
+
* Facets are separated by commas and each facet is encoded as `attributeName:value`.
|
|
767
|
+
* For example: `facetFilters=category:Book,author:John%20Doe`.
|
|
768
|
+
* You can also use a string array encoding (for example `["category:Book","author:John%20Doe"]`).
|
|
769
|
+
* - facets: List of object attributes that you want to use for faceting.
|
|
770
|
+
* Attributes are separated with a comma (for example `"category,author"` ).
|
|
771
|
+
* You can also use a JSON string array encoding (for example ["category","author"]).
|
|
772
|
+
* Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter.
|
|
773
|
+
* You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**.
|
|
774
|
+
* - queryType: select how the query words are interpreted, it can be one of the following value:
|
|
775
|
+
* - prefixAll: all query words are interpreted as prefixes,
|
|
776
|
+
* - prefixLast: only the last word is interpreted as a prefix (default behavior),
|
|
777
|
+
* - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
|
|
778
|
+
* - optionalWords: a string that contains the list of words that should be considered as optional when found in the query.
|
|
779
|
+
* The list of words is comma separated.
|
|
780
|
+
* - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set.
|
|
781
|
+
* This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter,
|
|
782
|
+
* all hits containing a duplicate value for the attributeForDistinct attribute are removed from results.
|
|
783
|
+
* For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best
|
|
784
|
+
* one is kept and others are removed.
|
|
785
|
+
* @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime.
|
|
786
|
+
*/
|
|
787
|
+
search: function(query, callback, args, delay) {
|
|
788
|
+
var indexObj = this;
|
|
789
|
+
var params = 'query=' + encodeURIComponent(query);
|
|
790
|
+
if (!this.as._isUndefined(args) && args != null) {
|
|
791
|
+
params = this.as._getSearchParams(args, params);
|
|
792
|
+
}
|
|
793
|
+
window.clearTimeout(indexObj.onDelayTrigger);
|
|
794
|
+
if (!this.as._isUndefined(delay) && delay != null && delay > 0) {
|
|
795
|
+
var onDelayTrigger = window.setTimeout( function() {
|
|
796
|
+
indexObj._search(params, callback);
|
|
797
|
+
}, delay);
|
|
798
|
+
indexObj.onDelayTrigger = onDelayTrigger;
|
|
799
|
+
} else {
|
|
800
|
+
this._search(params, callback);
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
|
|
804
|
+
/*
|
|
805
|
+
* Browse all index content
|
|
806
|
+
*
|
|
807
|
+
* @param page Pagination parameter used to select the page to retrieve.
|
|
808
|
+
* Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
|
|
809
|
+
* @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000.
|
|
810
|
+
*/
|
|
811
|
+
browse: function(page, callback, hitsPerPage) {
|
|
812
|
+
var indexObj = this;
|
|
813
|
+
var params = '?page=' + page;
|
|
814
|
+
if (!_.isUndefined(hitsPerPage)) {
|
|
815
|
+
params += '&hitsPerPage=' + hitsPerPage;
|
|
816
|
+
}
|
|
817
|
+
this.as._jsonRequest({ method: 'GET',
|
|
818
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params,
|
|
819
|
+
callback: callback });
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
/*
|
|
823
|
+
* Get transport layer for Typeahead.js
|
|
824
|
+
* @param args (optional) if set, contains an object with query parameters (see search for details)
|
|
825
|
+
* @param propertyName(optional) if set, contains the name of property that will be used for
|
|
826
|
+
*/
|
|
827
|
+
getTypeaheadTransport: function(args, valueOption) {
|
|
828
|
+
this.typeAheadArgs = args;
|
|
829
|
+
if (typeof valueOption !== 'undefined') {
|
|
830
|
+
this.typeAheadValueOption = valueOption;
|
|
831
|
+
}
|
|
832
|
+
return this;
|
|
833
|
+
},
|
|
834
|
+
/*
|
|
835
|
+
* Update parameter of transport layer for Typeahead.js
|
|
836
|
+
* @param args contains an object with query parameters (see search for details)
|
|
837
|
+
* @param propertyName(optional) if set, contains the name of property that will be used for
|
|
838
|
+
*/
|
|
839
|
+
setTypeaheadParams: function(args, valueOption) {
|
|
840
|
+
this.typeAHeadArgs = args;
|
|
841
|
+
if (typeof valueOption !== 'undefined') {
|
|
842
|
+
this.typeAheadValueOption = valueOption;
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
// Method used by Typeahead.js.
|
|
846
|
+
get: function(query, processRemoteData, that, cb, suggestions) {
|
|
847
|
+
self = this;
|
|
848
|
+
this.search(query, function(success, content) {
|
|
849
|
+
if (success) {
|
|
850
|
+
for (var i = 0; i < content.hits.length; ++i) {
|
|
851
|
+
// Add an attribute value with the first string
|
|
852
|
+
var obj = content.hits[i],
|
|
853
|
+
found = false;
|
|
854
|
+
|
|
855
|
+
if (typeof obj.value === 'undefined') {
|
|
856
|
+
if (self.typeAheadValueOption != null) {
|
|
857
|
+
if (typeof self.typeAheadValueOption === 'function') {
|
|
858
|
+
obj.value = self.typeAheadValueOption(obj);
|
|
859
|
+
found = true;
|
|
860
|
+
} else if (typeof obj[self.typeAheadValueOption] !== 'undefined') {
|
|
861
|
+
obj.value = obj[self.typeAheadValueOption];
|
|
496
862
|
found = true;
|
|
497
863
|
}
|
|
498
864
|
}
|
|
865
|
+
if (! found) {
|
|
866
|
+
for (var propertyName in obj) {
|
|
867
|
+
if (!found && obj.hasOwnProperty(propertyName) && typeof obj[propertyName] === 'string') {
|
|
868
|
+
obj.value = obj[propertyName];
|
|
869
|
+
found = true;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
499
873
|
}
|
|
874
|
+
suggestions.push(that._transformDatum(obj));
|
|
500
875
|
}
|
|
501
|
-
suggestions
|
|
876
|
+
cb && cb(suggestions);
|
|
502
877
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
},
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
878
|
+
}, self.typeAheadArgs);
|
|
879
|
+
return true;
|
|
880
|
+
},
|
|
881
|
+
/*
|
|
882
|
+
* Wait the publication of a task on the server.
|
|
883
|
+
* All server task are asynchronous and you can check with this method that the task is published.
|
|
884
|
+
*
|
|
885
|
+
* @param taskID the id of the task returned by server
|
|
886
|
+
* @param callback the result callback with with two arguments:
|
|
887
|
+
* success: boolean set to true if the request was successfull
|
|
888
|
+
* content: the server answer that contains the list of results
|
|
889
|
+
*/
|
|
890
|
+
waitTask: function(taskID, callback) {
|
|
891
|
+
var indexObj = this;
|
|
892
|
+
this.as._jsonRequest({ method: 'GET',
|
|
893
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID,
|
|
894
|
+
callback: function(success, body) {
|
|
895
|
+
if (success && body.status === 'published') {
|
|
515
896
|
callback(true, body);
|
|
516
897
|
} else if (success && body.pendingTask) {
|
|
517
898
|
return indexObj.waitTask(taskID, callback);
|
|
518
899
|
} else {
|
|
519
900
|
callback(false, body);
|
|
520
901
|
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
902
|
+
}});
|
|
903
|
+
},
|
|
904
|
+
|
|
905
|
+
/*
|
|
906
|
+
* This function deletes the index content. Settings and index specific API keys are kept untouched.
|
|
907
|
+
*
|
|
908
|
+
* @param callback (optional) the result callback with two arguments
|
|
909
|
+
* success: boolean set to true if the request was successfull
|
|
910
|
+
* content: the settings object or the error message if a failure occured
|
|
911
|
+
*/
|
|
912
|
+
clearIndex: function(callback) {
|
|
913
|
+
var indexObj = this;
|
|
914
|
+
this.as._jsonRequest({ method: 'POST',
|
|
915
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear',
|
|
916
|
+
callback: callback });
|
|
917
|
+
},
|
|
918
|
+
/*
|
|
919
|
+
* Get settings of this index
|
|
920
|
+
*
|
|
921
|
+
* @param callback (optional) the result callback with two arguments
|
|
922
|
+
* success: boolean set to true if the request was successfull
|
|
923
|
+
* content: the settings object or the error message if a failure occured
|
|
924
|
+
*/
|
|
925
|
+
getSettings: function(callback) {
|
|
926
|
+
var indexObj = this;
|
|
927
|
+
this.as._jsonRequest({ method: 'GET',
|
|
928
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
|
|
929
|
+
callback: callback });
|
|
930
|
+
},
|
|
931
|
+
|
|
932
|
+
/*
|
|
933
|
+
* Set settings for this index
|
|
934
|
+
*
|
|
935
|
+
* @param settigns the settings object that can contains :
|
|
936
|
+
* - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3).
|
|
937
|
+
* - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7).
|
|
938
|
+
* - hitsPerPage: (integer) the number of hits per page (default = 10).
|
|
939
|
+
* - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
|
|
940
|
+
* If set to null, all attributes are retrieved.
|
|
941
|
+
* - attributesToHighlight: (array of strings) default list of attributes to highlight.
|
|
942
|
+
* If set to null, all indexed attributes are highlighted.
|
|
943
|
+
* - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords).
|
|
944
|
+
* By default no snippet is computed. If set to null, no snippet is computed.
|
|
945
|
+
* - attributesToIndex: (array of strings) the list of fields you want to index.
|
|
946
|
+
* If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results.
|
|
947
|
+
* This parameter has two important uses:
|
|
948
|
+
* - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to
|
|
949
|
+
* retrieve it but you don't want to search in the base64 string.
|
|
950
|
+
* - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of
|
|
951
|
+
* the list will be considered more important than matches in attributes further down the list.
|
|
952
|
+
* In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable
|
|
953
|
+
* this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"].
|
|
954
|
+
* - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
|
|
955
|
+
* All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting.
|
|
956
|
+
* - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled
|
|
957
|
+
* in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results.
|
|
958
|
+
* For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best one is kept and others are removed.
|
|
959
|
+
* - ranking: (array of strings) controls the way results are sorted.
|
|
960
|
+
* We have six available criteria:
|
|
961
|
+
* - typo: sort according to number of typos,
|
|
962
|
+
* - geo: sort according to decreassing distance when performing a geo-location based search,
|
|
963
|
+
* - proximity: sort according to the proximity of query words in hits,
|
|
964
|
+
* - attribute: sort according to the order of attributes defined by attributesToIndex,
|
|
965
|
+
* - exact:
|
|
966
|
+
* - if the user query contains one word: sort objects having an attribute that is exactly the query word before others.
|
|
967
|
+
* For example if you search for the "V" TV show, you want to find it with the "V" query and avoid to have all popular TV
|
|
968
|
+
* show starting by the v letter before it.
|
|
969
|
+
* - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix).
|
|
970
|
+
* - custom: sort according to a user defined formula set in **customRanking** attribute.
|
|
971
|
+
* The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
|
|
972
|
+
* - customRanking: (array of strings) lets you specify part of the ranking.
|
|
973
|
+
* The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator.
|
|
974
|
+
* For example `"customRanking" => ["desc(population)", "asc(name)"]`
|
|
975
|
+
* - queryType: Select how the query words are interpreted, it can be one of the following value:
|
|
976
|
+
* - prefixAll: all query words are interpreted as prefixes,
|
|
977
|
+
* - prefixLast: only the last word is interpreted as a prefix (default behavior),
|
|
978
|
+
* - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
|
|
979
|
+
* - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to "<em>").
|
|
980
|
+
* - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to "</em>").
|
|
981
|
+
* - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query.
|
|
982
|
+
* @param callback (optional) the result callback with two arguments
|
|
983
|
+
* success: boolean set to true if the request was successfull
|
|
984
|
+
* content: the server answer or the error message if a failure occured
|
|
985
|
+
*/
|
|
986
|
+
setSettings: function(settings, callback) {
|
|
987
|
+
var indexObj = this;
|
|
988
|
+
this.as._jsonRequest({ method: 'PUT',
|
|
989
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
|
|
990
|
+
body: settings,
|
|
991
|
+
callback: callback });
|
|
992
|
+
},
|
|
993
|
+
/*
|
|
994
|
+
* List all existing user keys associated to this index
|
|
995
|
+
*
|
|
996
|
+
* @param callback the result callback with two arguments
|
|
997
|
+
* success: boolean set to true if the request was successfull
|
|
998
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
999
|
+
*/
|
|
1000
|
+
listUserKeys: function(callback) {
|
|
1001
|
+
var indexObj = this;
|
|
1002
|
+
this.as._jsonRequest({ method: 'GET',
|
|
1003
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
|
|
1004
|
+
callback: callback });
|
|
1005
|
+
},
|
|
1006
|
+
/*
|
|
1007
|
+
* Get ACL of a user key associated to this index
|
|
1008
|
+
*
|
|
1009
|
+
* @param callback the result callback with two arguments
|
|
1010
|
+
* success: boolean set to true if the request was successfull
|
|
1011
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
1012
|
+
*/
|
|
1013
|
+
getUserKeyACL: function(key, callback) {
|
|
1014
|
+
var indexObj = this;
|
|
1015
|
+
this.as._jsonRequest({ method: 'GET',
|
|
1016
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
|
|
1017
|
+
callback: callback });
|
|
1018
|
+
},
|
|
1019
|
+
/*
|
|
1020
|
+
* Delete an existing user key associated to this index
|
|
1021
|
+
*
|
|
1022
|
+
* @param callback the result callback with two arguments
|
|
1023
|
+
* success: boolean set to true if the request was successfull
|
|
1024
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
1025
|
+
*/
|
|
1026
|
+
deleteUserKey: function(key, callback) {
|
|
1027
|
+
var indexObj = this;
|
|
1028
|
+
this.as._jsonRequest({ method: 'DELETE',
|
|
1029
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
|
|
1030
|
+
callback: callback });
|
|
1031
|
+
},
|
|
1032
|
+
/*
|
|
1033
|
+
* Add an existing user key associated to this index
|
|
1034
|
+
*
|
|
1035
|
+
* @param acls the list of ACL for this key. Defined by an array of strings that
|
|
1036
|
+
* can contains the following values:
|
|
1037
|
+
* - search: allow to search (https and http)
|
|
1038
|
+
* - addObject: allows to add/update an object in the index (https only)
|
|
1039
|
+
* - deleteObject : allows to delete an existing object (https only)
|
|
1040
|
+
* - deleteIndex : allows to delete index content (https only)
|
|
1041
|
+
* - settings : allows to get index settings (https only)
|
|
1042
|
+
* - editSettings : allows to change index settings (https only)
|
|
1043
|
+
* @param callback the result callback with two arguments
|
|
1044
|
+
* success: boolean set to true if the request was successfull
|
|
1045
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
1046
|
+
*/
|
|
1047
|
+
addUserKey: function(acls, callback) {
|
|
1048
|
+
var indexObj = this;
|
|
1049
|
+
var aclsObject = {};
|
|
1050
|
+
aclsObject.acl = acls;
|
|
1051
|
+
this.as._jsonRequest({ method: 'POST',
|
|
1052
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
|
|
1053
|
+
body: aclsObject,
|
|
1054
|
+
callback: callback });
|
|
1055
|
+
},
|
|
1056
|
+
/*
|
|
1057
|
+
* Add an existing user key associated to this index
|
|
1058
|
+
*
|
|
1059
|
+
* @param acls the list of ACL for this key. Defined by an array of strings that
|
|
1060
|
+
* can contains the following values:
|
|
1061
|
+
* - search: allow to search (https and http)
|
|
1062
|
+
* - addObject: allows to add/update an object in the index (https only)
|
|
1063
|
+
* - deleteObject : allows to delete an existing object (https only)
|
|
1064
|
+
* - deleteIndex : allows to delete index content (https only)
|
|
1065
|
+
* - settings : allows to get index settings (https only)
|
|
1066
|
+
* - editSettings : allows to change index settings (https only)
|
|
1067
|
+
* @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
|
|
1068
|
+
* @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
|
|
1069
|
+
* @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
|
|
1070
|
+
* @param callback the result callback with two arguments
|
|
1071
|
+
* success: boolean set to true if the request was successfull
|
|
1072
|
+
* content: the server answer with user keys list or error description if success is false.
|
|
1073
|
+
*/
|
|
1074
|
+
addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
|
|
1075
|
+
var indexObj = this;
|
|
1076
|
+
var aclsObject = {};
|
|
1077
|
+
aclsObject.acl = acls;
|
|
1078
|
+
aclsObject.validity = validity;
|
|
1079
|
+
aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
|
|
1080
|
+
aclsObject.maxHitsPerQuery = maxHitsPerQuery;
|
|
1081
|
+
this.as._jsonRequest({ method: 'POST',
|
|
1082
|
+
url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
|
|
1083
|
+
body: aclsObject,
|
|
1084
|
+
callback: callback });
|
|
1085
|
+
},
|
|
1086
|
+
///
|
|
1087
|
+
/// Internal methods only after this line
|
|
1088
|
+
///
|
|
1089
|
+
_search: function(params, callback) {
|
|
1090
|
+
this.as._jsonRequest({ cache: this.cache,
|
|
1091
|
+
method: 'POST',
|
|
1092
|
+
url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
|
|
1093
|
+
body: {params: params, apiKey: this.as.apiKey, appID: this.as.applicationID},
|
|
1094
|
+
callback: callback });
|
|
1095
|
+
},
|
|
1096
|
+
|
|
1097
|
+
// internal attributes
|
|
1098
|
+
as: null,
|
|
1099
|
+
indexName: null,
|
|
1100
|
+
cache: {},
|
|
1101
|
+
typeAheadArgs: null,
|
|
1102
|
+
typeAheadValueOption: null,
|
|
1103
|
+
emptyConstructor: function() {}
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
/*
|
|
1107
|
+
* Copyright (c) 2014 Algolia
|
|
1108
|
+
* http://www.algolia.com/
|
|
1109
|
+
*
|
|
1110
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1111
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
1112
|
+
* in the Software without restriction, including without limitation the rights
|
|
1113
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1114
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
1115
|
+
* furnished to do so, subject to the following conditions:
|
|
1116
|
+
*
|
|
1117
|
+
* The above copyright notice and this permission notice shall be included in
|
|
1118
|
+
* all copies or substantial portions of the Software.
|
|
1119
|
+
*
|
|
1120
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1121
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1122
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1123
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1124
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1125
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
1126
|
+
* THE SOFTWARE.
|
|
1127
|
+
*/
|
|
1128
|
+
|
|
1129
|
+
(function($) {
|
|
1130
|
+
var self;
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Algolia Search Helper providing faceting and disjunctive faceting
|
|
1134
|
+
* @param {AlgoliaSearch} client an AlgoliaSearch client
|
|
1135
|
+
* @param {string} index the index name to query
|
|
1136
|
+
* @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets
|
|
1137
|
+
*/
|
|
1138
|
+
window.AlgoliaSearchHelper = function(client, index, options) {
|
|
1139
|
+
/// Default options
|
|
1140
|
+
var defaults = {
|
|
1141
|
+
facets: [], // list of facets to compute
|
|
1142
|
+
disjunctiveFacets: [], // list of disjunctive facets to compute
|
|
1143
|
+
hitsPerPage: 20 // number of hits per page
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
this.init(client, index, $.extend({}, defaults, options));
|
|
1147
|
+
self = this;
|
|
1148
|
+
};
|
|
1149
|
+
|
|
1150
|
+
AlgoliaSearchHelper.prototype = {
|
|
1151
|
+
/**
|
|
1152
|
+
* Initialize a new AlgoliaSearchHelper
|
|
1153
|
+
* @param {AlgoliaSearch} client an AlgoliaSearch client
|
|
1154
|
+
* @param {string} index the index name to query
|
|
1155
|
+
* @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets
|
|
1156
|
+
* @return {AlgoliaSearchHelper}
|
|
1157
|
+
*/
|
|
1158
|
+
init: function(client, index, options) {
|
|
1159
|
+
this.client = client;
|
|
1160
|
+
this.index = index;
|
|
1161
|
+
this.options = options;
|
|
1162
|
+
this.page = 0;
|
|
1163
|
+
this.refinements = {};
|
|
1164
|
+
this.disjunctiveRefinements = {};
|
|
523
1165
|
},
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1166
|
+
|
|
1167
|
+
/**
|
|
1168
|
+
* Perform a query
|
|
1169
|
+
* @param {string} q the user query
|
|
1170
|
+
* @param {function} searchCallback the result callback called with two arguments:
|
|
1171
|
+
* success: boolean set to true if the request was successfull
|
|
1172
|
+
* content: the query answer with an extra 'disjunctiveFacets' attribute
|
|
1173
|
+
*/
|
|
1174
|
+
search: function(q, searchCallback) {
|
|
1175
|
+
this.q = q;
|
|
1176
|
+
this.searchCallback = searchCallback;
|
|
1177
|
+
this.page = 0;
|
|
1178
|
+
this.refinements = {};
|
|
1179
|
+
this.disjunctiveRefinements = {};
|
|
1180
|
+
this._search();
|
|
531
1181
|
},
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Toggle refinement state of a facet
|
|
1185
|
+
* @param {string} facet the facet to refine
|
|
1186
|
+
* @param {string} value the associated value
|
|
1187
|
+
* @return {boolean} true if the facet has been found
|
|
1188
|
+
*/
|
|
1189
|
+
toggleRefine: function(facet, value) {
|
|
1190
|
+
for (var i = 0; i < this.options.facets.length; ++i) {
|
|
1191
|
+
if (this.options.facets[i] == facet) {
|
|
1192
|
+
var refinement = facet + ':' + value;
|
|
1193
|
+
this.refinements[refinement] = !this.refinements[refinement];
|
|
1194
|
+
this.page = 0;
|
|
1195
|
+
this._search();
|
|
1196
|
+
return true;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {};
|
|
1200
|
+
for (var j = 0; j < this.options.disjunctiveFacets.length; ++j) {
|
|
1201
|
+
if (this.options.disjunctiveFacets[j] == facet) {
|
|
1202
|
+
this.disjunctiveRefinements[facet][value] = !this.disjunctiveRefinements[facet][value];
|
|
1203
|
+
this.page = 0;
|
|
1204
|
+
this._search();
|
|
1205
|
+
return true;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return false;
|
|
539
1209
|
},
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Check the refinement state of a facet
|
|
1213
|
+
* @param {string} facet the facet
|
|
1214
|
+
* @param {string} value the associated value
|
|
1215
|
+
* @return {boolean} true if refined
|
|
1216
|
+
*/
|
|
1217
|
+
isRefined: function(facet, value) {
|
|
1218
|
+
var refinement = facet + ':' + value;
|
|
1219
|
+
if (this.refinements[refinement]) {
|
|
1220
|
+
return true;
|
|
1221
|
+
}
|
|
1222
|
+
if (this.disjunctiveRefinements[facet] && this.disjunctiveRefinements[facet][value]) {
|
|
1223
|
+
return true;
|
|
1224
|
+
}
|
|
1225
|
+
return false;
|
|
548
1226
|
},
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
});
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Go to next page
|
|
1230
|
+
*/
|
|
1231
|
+
nextPage: function() {
|
|
1232
|
+
this._gotoPage(this.page + 1);
|
|
556
1233
|
},
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Go to previous page
|
|
1237
|
+
*/
|
|
1238
|
+
previousPage: function() {
|
|
1239
|
+
if (this.page > 0) {
|
|
1240
|
+
this._gotoPage(this.page - 1);
|
|
1241
|
+
}
|
|
564
1242
|
},
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1243
|
+
|
|
1244
|
+
///////////// PRIVATE
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* Goto a page
|
|
1248
|
+
* @param {integer} page The page number
|
|
1249
|
+
*/
|
|
1250
|
+
_gotoPage: function(page) {
|
|
1251
|
+
this.page = page;
|
|
1252
|
+
this._search();
|
|
572
1253
|
},
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Perform the underlying queries
|
|
1257
|
+
*/
|
|
1258
|
+
_search: function() {
|
|
1259
|
+
this.client.startQueriesBatch();
|
|
1260
|
+
this.client.addQueryInBatch(this.index, this.q, this._getHitsSearchParams());
|
|
1261
|
+
for (var i = 0; i < this.options.disjunctiveFacets.length; ++i) {
|
|
1262
|
+
this.client.addQueryInBatch(this.index, this.q, this._getDisjunctiveFacetSearchParams(this.options.disjunctiveFacets[i]));
|
|
1263
|
+
}
|
|
1264
|
+
this.client.sendQueriesBatch(function(success, content) {
|
|
1265
|
+
if (!success) {
|
|
1266
|
+
self.searchCallback(false, content);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
var aggregatedAnswer = content.results[0];
|
|
1270
|
+
aggregatedAnswer.disjunctiveFacets = {};
|
|
1271
|
+
for (var i = 1; i < content.results.length; ++i) {
|
|
1272
|
+
for (var facet in content.results[i].facets) {
|
|
1273
|
+
aggregatedAnswer.disjunctiveFacets[facet] = content.results[i].facets[facet];
|
|
1274
|
+
if (self.disjunctiveRefinements[facet]) {
|
|
1275
|
+
for (var value in self.disjunctiveRefinements[facet]) {
|
|
1276
|
+
if (!aggregatedAnswer.disjunctiveFacets[facet][value] && self.disjunctiveRefinements[facet][value]) {
|
|
1277
|
+
aggregatedAnswer.disjunctiveFacets[facet][value] = 0;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
self.searchCallback(true, aggregatedAnswer);
|
|
1284
|
+
});
|
|
583
1285
|
},
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
this.
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
},
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Build search parameters used to fetch hits
|
|
1289
|
+
* @return {hash}
|
|
1290
|
+
*/
|
|
1291
|
+
_getHitsSearchParams: function() {
|
|
1292
|
+
return {
|
|
1293
|
+
hitsPerPage: this.options.hitsPerPage,
|
|
1294
|
+
page: this.page,
|
|
1295
|
+
facets: this.options.facets,
|
|
1296
|
+
facetFilters: this._getFacetFilters()
|
|
1297
|
+
};
|
|
1298
|
+
},
|
|
1299
|
+
|
|
1300
|
+
/**
|
|
1301
|
+
* Build search parameters used to fetch a disjunctive facet
|
|
1302
|
+
* @param {string} facet the associated facet name
|
|
1303
|
+
* @return {hash}
|
|
1304
|
+
*/
|
|
1305
|
+
_getDisjunctiveFacetSearchParams: function(facet) {
|
|
1306
|
+
return {
|
|
1307
|
+
hitsPerPage: 1,
|
|
1308
|
+
page: 0,
|
|
1309
|
+
facets: facet,
|
|
1310
|
+
facetFilters: this._getFacetFilters(facet)
|
|
1311
|
+
};
|
|
1312
|
+
},
|
|
1313
|
+
|
|
1314
|
+
/**
|
|
1315
|
+
* Build facetFilters parameter based on current refinements
|
|
1316
|
+
* @param {string} facet if set, the current disjunctive facet
|
|
1317
|
+
* @return {hash}
|
|
1318
|
+
*/
|
|
1319
|
+
_getFacetFilters: function(facet) {
|
|
1320
|
+
var facetFilters = [];
|
|
1321
|
+
for (var refinement in this.refinements) {
|
|
1322
|
+
if (this.refinements[refinement]) {
|
|
1323
|
+
facetFilters.push(refinement);
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
for (var disjunctiveRefinement in this.disjunctiveRefinements) {
|
|
1327
|
+
if (disjunctiveRefinement != facet) {
|
|
1328
|
+
var refinements = [];
|
|
1329
|
+
for (var value in this.disjunctiveRefinements[disjunctiveRefinement]) {
|
|
1330
|
+
if (this.disjunctiveRefinements[disjunctiveRefinement][value]) {
|
|
1331
|
+
refinements.push(disjunctiveRefinement + ':' + value);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (refinements.length > 0) {
|
|
1335
|
+
facetFilters.push(refinements);
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
return facetFilters;
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
})(jQuery);
|