PageRenderTime 50ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/algoliasearch/2.4.0/algoliasearch.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 979 lines | 498 code | 29 blank | 452 comment | 110 complexity | 903e3529634ce5c17b0114a9eb745234 MD5 | raw file
  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.
  22. */
  23. var ALGOLIA_VERSION = '2.4.0';
  24. /*
  25. * Copyright (c) 2013 Algolia
  26. * http://www.algolia.com/
  27. *
  28. * Permission is hereby granted, free of charge, to any person obtaining a copy
  29. * of this software and associated documentation files (the "Software"), to deal
  30. * in the Software without restriction, including without limitation the rights
  31. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  32. * copies of the Software, and to permit persons to whom the Software is
  33. * furnished to do so, subject to the following conditions:
  34. *
  35. * The above copyright notice and this permission notice shall be included in
  36. * all copies or substantial portions of the Software.
  37. *
  38. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  39. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  40. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  41. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  42. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  43. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  44. * THE SOFTWARE.
  45. */
  46. /*
  47. * Algolia Search library initialization
  48. * @param applicationID the application ID you have in your admin interface
  49. * @param apiKey a valid API key for the service
  50. * @param method specify if the protocol used is http or https (http by default to make the first search query faster).
  51. * You need to use https is you are doing something else than just search queries.
  52. * @param resolveDNS let you disable first empty query that is launch to warmup the service
  53. * @param hostsArray (optionnal) the list of hosts that you have received for the service
  54. */
  55. var AlgoliaSearch = function(applicationID, apiKey, method, resolveDNS, hostsArray) {
  56. this.applicationID = applicationID;
  57. this.apiKey = apiKey;
  58. if (this._isUndefined(hostsArray)) {
  59. hostsArray = [applicationID + '-1.algolia.io',
  60. applicationID + '-2.algolia.io',
  61. applicationID + '-3.algolia.io'];
  62. }
  63. this.hosts = [];
  64. // Add hosts in random order
  65. for (var i = 0; i < hostsArray.length; ++i) {
  66. if (Math.random() > 0.5) {
  67. this.hosts.reverse();
  68. }
  69. if (this._isUndefined(method) || method == null) {
  70. this.hosts.push(('https:' == document.location.protocol ? 'https' : 'http') + '://' + hostsArray[i]);
  71. } else if (method === 'https' || method === 'HTTPS') {
  72. this.hosts.push('https://' + hostsArray[i]);
  73. } else {
  74. this.hosts.push('http://' + hostsArray[i]);
  75. }
  76. }
  77. if (Math.random() > 0.5) {
  78. this.hosts.reverse();
  79. }
  80. if (this._isUndefined(resolveDNS) || resolveDNS) {
  81. // Perform a call to solve DNS (avoid to slow down the first user query)
  82. this._jsonRequest({ method: 'GET',
  83. url: '/1/isalive' });
  84. }
  85. this.extraHeaders = [];
  86. };
  87. function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) {
  88. function _getHitAxplainationForOneAttr_recurse(obj, foundWords) {
  89. if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) {
  90. var match = false;
  91. for (var j = 0; j < obj.matchedWords.length; ++j) {
  92. var word = obj.matchedWords[j];
  93. if (!(word in foundWords)) {
  94. foundWords[word] = 1;
  95. match = true;
  96. }
  97. }
  98. return match ? [obj.value] : [];
  99. } else if (obj instanceof Array) {
  100. var res = [];
  101. for (var i = 0; i < obj.length; ++i) {
  102. var array = _getHitAxplainationForOneAttr_recurse(obj[i], foundWords);
  103. res = res.concat(array);
  104. }
  105. return res;
  106. } else if (typeof obj === 'object') {
  107. var res = [];
  108. for (prop in obj) {
  109. if (obj.hasOwnProperty(prop)){
  110. res = res.concat(_getHitAxplainationForOneAttr_recurse(obj[prop], foundWords));
  111. }
  112. }
  113. return res;
  114. }
  115. return [];
  116. }
  117. function _getHitAxplainationForOneAttr(hit, foundWords, attr) {
  118. if (attr.indexOf('.') === -1) {
  119. if (attr in hit._highlightResult) {
  120. return _getHitAxplainationForOneAttr_recurse(hit._highlightResult[attr], foundWords);
  121. }
  122. return [];
  123. }
  124. var array = attr.split('.');
  125. var obj = hit._highlightResult;
  126. for (var i = 0; i < array.length; ++i) {
  127. if (array[i] in obj) {
  128. obj = obj[array[i]];
  129. } else {
  130. return [];
  131. }
  132. }
  133. return _getHitAxplainationForOneAttr_recurse(obj, foundWords);
  134. }
  135. var res = {};
  136. var foundWords = {};
  137. var title = _getHitAxplainationForOneAttr(hit, foundWords, titleAttribute);
  138. res.title = (title.length > 0) ? title[0] : "";
  139. res.subtitles = [];
  140. if (typeof otherAttributes !== 'undefined') {
  141. for (var i = 0; i < otherAttributes.length; ++i) {
  142. var attr = _getHitAxplainationForOneAttr(hit, foundWords, otherAttributes[i]);
  143. for (var j = 0; j < attr.length; ++j) {
  144. res.subtitles.push({ attr: otherAttributes[i], value: attr[j] });
  145. }
  146. }
  147. }
  148. return res;
  149. }
  150. AlgoliaSearch.prototype = {
  151. /*
  152. * Delete an index
  153. *
  154. * @param indexName the name of index to delete
  155. * @param callback the result callback with two arguments
  156. * success: boolean set to true if the request was successfull
  157. * content: the server answer that contains the task ID
  158. */
  159. deleteIndex: function(indexName, callback) {
  160. this._jsonRequest({ method: 'DELETE',
  161. url: '/1/indexes/' + encodeURIComponent(indexName),
  162. callback: callback });
  163. },
  164. /**
  165. * Move an existing index.
  166. * @param srcIndexName the name of index to copy.
  167. * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
  168. * @param callback the result callback with two arguments
  169. * success: boolean set to true if the request was successfull
  170. * content: the server answer that contains the task ID
  171. */
  172. moveIndex: function(srcIndexName, dstIndexName, callback) {
  173. var postObj = {operation: 'move', destination: dstIndexName};
  174. this._jsonRequest({ method: 'POST',
  175. url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
  176. body: postObj,
  177. callback: callback });
  178. },
  179. /**
  180. * Copy an existing index.
  181. * @param srcIndexName the name of index to copy.
  182. * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
  183. * @param callback the result callback with two arguments
  184. * success: boolean set to true if the request was successfull
  185. * content: the server answer that contains the task ID
  186. */
  187. copyIndex: function(srcIndexName, dstIndexName, callback) {
  188. var postObj = {operation: 'copy', destination: dstIndexName};
  189. this._jsonRequest({ method: 'POST',
  190. url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
  191. body: postObj,
  192. callback: callback });
  193. },
  194. /**
  195. * Return last log entries.
  196. * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
  197. * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000.
  198. * @param callback the result callback with two arguments
  199. * success: boolean set to true if the request was successfull
  200. * content: the server answer that contains the task ID
  201. */
  202. getLogs: function(callback, offset, length) {
  203. if (this._isUndefined(offset)) {
  204. offset = 0;
  205. }
  206. if (this._isUndefined(length)) {
  207. length = 10;
  208. }
  209. this._jsonRequest({ method: 'GET',
  210. url: '/1/logs?offset=' + offset + '&length=' + length,
  211. callback: callback });
  212. },
  213. /*
  214. * List all existing indexes
  215. *
  216. * @param callback the result callback with two arguments
  217. * success: boolean set to true if the request was successfull
  218. * content: the server answer with index list or error description if success is false.
  219. */
  220. listIndexes: function(callback) {
  221. this._jsonRequest({ method: 'GET',
  222. url: '/1/indexes/',
  223. callback: callback });
  224. },
  225. /*
  226. * Get the index object initialized
  227. *
  228. * @param indexName the name of index
  229. * @param callback the result callback with one argument (the Index instance)
  230. */
  231. initIndex: function(indexName) {
  232. return new this.Index(this, indexName);
  233. },
  234. /*
  235. * List all existing user keys with their associated ACLs
  236. *
  237. * @param callback the result callback with two arguments
  238. * success: boolean set to true if the request was successfull
  239. * content: the server answer with user keys list or error description if success is false.
  240. */
  241. listUserKeys: function(callback) {
  242. this._jsonRequest({ method: 'GET',
  243. url: '/1/keys',
  244. callback: callback });
  245. },
  246. /*
  247. * Get ACL of a user key
  248. *
  249. * @param callback the result callback with two arguments
  250. * success: boolean set to true if the request was successfull
  251. * content: the server answer with user keys list or error description if success is false.
  252. */
  253. getUserKeyACL: function(key, callback) {
  254. this._jsonRequest({ method: 'GET',
  255. url: '/1/keys/' + key,
  256. callback: callback });
  257. },
  258. /*
  259. * Delete an existing user key
  260. *
  261. * @param callback the result callback with two arguments
  262. * success: boolean set to true if the request was successfull
  263. * content: the server answer with user keys list or error description if success is false.
  264. */
  265. deleteUserKey: function(key, callback) {
  266. this._jsonRequest({ method: 'DELETE',
  267. url: '/1/keys/' + key,
  268. callback: callback });
  269. },
  270. /*
  271. * Add an existing user key
  272. *
  273. * @param acls the list of ACL for this key. Defined by an array of strings that
  274. * can contains the following values:
  275. * - search: allow to search (https and http)
  276. * - addObject: allows to add/update an object in the index (https only)
  277. * - deleteObject : allows to delete an existing object (https only)
  278. * - deleteIndex : allows to delete index content (https only)
  279. * - settings : allows to get index settings (https only)
  280. * - editSettings : allows to change index settings (https only)
  281. * @param callback the result callback with two arguments
  282. * success: boolean set to true if the request was successfull
  283. * content: the server answer with user keys list or error description if success is false.
  284. */
  285. addUserKey: function(acls, callback) {
  286. var aclsObject = {};
  287. aclsObject.acl = acls;
  288. this._jsonRequest({ method: 'POST',
  289. url: '/1/keys',
  290. body: aclsObject,
  291. callback: callback });
  292. },
  293. /*
  294. * Add an existing user key
  295. *
  296. * @param acls the list of ACL for this key. Defined by an array of strings that
  297. * can contains the following values:
  298. * - search: allow to search (https and http)
  299. * - addObject: allows to add/update an object in the index (https only)
  300. * - deleteObject : allows to delete an existing object (https only)
  301. * - deleteIndex : allows to delete index content (https only)
  302. * - settings : allows to get index settings (https only)
  303. * - editSettings : allows to change index settings (https only)
  304. * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
  305. * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
  306. * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
  307. * @param callback the result callback with two arguments
  308. * success: boolean set to true if the request was successfull
  309. * content: the server answer with user keys list or error description if success is false.
  310. */
  311. addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
  312. var indexObj = this;
  313. var aclsObject = {};
  314. aclsObject.acl = acls;
  315. aclsObject.validity = validity;
  316. aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
  317. aclsObject.maxHitsPerQuery = maxHitsPerQuery;
  318. this._jsonRequest({ method: 'POST',
  319. url: '/1/indexes/' + indexObj.indexName + '/keys',
  320. body: aclsObject,
  321. callback: callback });
  322. },
  323. /*
  324. * Initialize a new batch of search queries
  325. */
  326. startQueriesBatch: function() {
  327. this.batch = [];
  328. },
  329. /*
  330. * Add a search query in the batch
  331. *
  332. * @param query the full text query
  333. * @param args (optional) if set, contains an object with query parameters:
  334. * - attributes: an array of object attribute names to retrieve
  335. * (if not set all attributes are retrieve)
  336. * - attributesToHighlight: an array of object attribute names to highlight
  337. * (if not set indexed attributes are highlighted)
  338. * - minWordSizefor1Typo: the minimum number of characters to accept one typo.
  339. * Defaults to 3.
  340. * - minWordSizefor2Typos: the minimum number of characters to accept two typos.
  341. * Defaults to 7.
  342. * - getRankingInfo: if set, the result hits will contain ranking information in
  343. * _rankingInfo attribute
  344. * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0.
  345. * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10.
  346. */
  347. addQueryInBatch: function(indexName, query, args) {
  348. var params = 'query=' + encodeURIComponent(query);
  349. if (!this._isUndefined(args) && args != null) {
  350. params = this._getSearchParams(args, params);
  351. }
  352. this.batch.push({ indexName: indexName, params: params });
  353. },
  354. /*
  355. * Clear all queries in cache
  356. */
  357. clearCache: function() {
  358. this.cache = {};
  359. },
  360. /*
  361. * Launch the batch of queries using XMLHttpRequest.
  362. * (Optimized for browser using a POST query to minimize number of OPTIONS queries)
  363. *
  364. * @param callback the function that will receive results
  365. * @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime.
  366. */
  367. sendQueriesBatch: function(callback, delay) {
  368. var as = this;
  369. var params = {requests: [], apiKey: this.apiKey, appID: this.applicationID};
  370. for (var i = 0; i < as.batch.length; ++i) {
  371. params.requests.push(as.batch[i]);
  372. }
  373. window.clearTimeout(as.onDelayTrigger);
  374. if (!this._isUndefined(delay) && delay != null && delay > 0) {
  375. var onDelayTrigger = window.setTimeout( function() {
  376. as._sendQueriesBatch(params, callback);
  377. }, delay);
  378. as.onDelayTrigger = onDelayTrigger;
  379. } else {
  380. this._sendQueriesBatch(params, callback);
  381. }
  382. },
  383. /*
  384. * Index class constructor.
  385. * You should not use this method directly but use initIndex() function
  386. */
  387. Index: function(algoliasearch, indexName) {
  388. this.indexName = indexName;
  389. this.as = algoliasearch;
  390. this.typeAheadArgs = null;
  391. this.typeAheadValueOption = null;
  392. },
  393. setExtraHeader: function(key, value) {
  394. this.extraHeaders.push({ key: key, value: value});
  395. },
  396. _sendQueriesBatch: function(params, callback) {
  397. this._jsonRequest({ cache: this.cache,
  398. method: 'POST',
  399. url: '/1/indexes/*/queries',
  400. body: params,
  401. callback: callback });
  402. },
  403. /*
  404. * Wrapper that try all hosts to maximize the quality of service
  405. */
  406. _jsonRequest: function(opts) {
  407. var self = this;
  408. var callback = opts.callback;
  409. var cache = null;
  410. var cacheID = opts.url;
  411. if (!this._isUndefined(opts.body)) {
  412. cacheID = opts.url + '_body_' + JSON.stringify(opts.body);
  413. }
  414. if (!this._isUndefined(opts.cache)) {
  415. cache = opts.cache;
  416. if (!this._isUndefined(cache[cacheID])) {
  417. if (!this._isUndefined(callback)) {
  418. callback(true, cache[cacheID]);
  419. }
  420. return;
  421. }
  422. }
  423. var impl = function(position) {
  424. var idx = 0;
  425. if (!self._isUndefined(position)) {
  426. idx = position;
  427. }
  428. if (self.hosts.length <= idx) {
  429. if (!self._isUndefined(callback)) {
  430. callback(false, { message: 'Cannot contact server'});
  431. }
  432. return;
  433. }
  434. opts.callback = function(retry, success, res, body) {
  435. if (!success && !self._isUndefined(body)) {
  436. console.log('Error: ' + body.message);
  437. }
  438. if (success && !self._isUndefined(opts.cache)) {
  439. cache[cacheID] = body;
  440. }
  441. if (!success && retry && (idx + 1) < self.hosts.length) {
  442. impl(idx + 1);
  443. } else {
  444. if (!self._isUndefined(callback)) {
  445. callback(success, body);
  446. }
  447. }
  448. };
  449. opts.hostname = self.hosts[idx];
  450. self._jsonRequestByHost(opts);
  451. };
  452. impl();
  453. },
  454. _jsonRequestByHost: function(opts) {
  455. var body = null;
  456. var self = this;
  457. if (!this._isUndefined(opts.body)) {
  458. body = JSON.stringify(opts.body);
  459. }
  460. var url = opts.hostname + opts.url;
  461. var xmlHttp = null;
  462. xmlHttp = new XMLHttpRequest();
  463. if ('withCredentials' in xmlHttp) {
  464. xmlHttp.open(opts.method, url , true);
  465. xmlHttp.setRequestHeader('X-Algolia-API-Key', this.apiKey);
  466. xmlHttp.setRequestHeader('X-Algolia-Application-Id', this.applicationID);
  467. for (var i = 0; i < this.extraHeaders.length; ++i) {
  468. xmlHttp.setRequestHeader(this.extraHeaders[i].key, this.extraHeaders[i].value);
  469. }
  470. if (body != null) {
  471. xmlHttp.setRequestHeader('Content-type', 'application/json');
  472. }
  473. } else if (typeof XDomainRequest != 'undefined') {
  474. // Handle IE8/IE9
  475. // XDomainRequest only exists in IE, and is IE's way of making CORS requests.
  476. xmlHttp = new XDomainRequest();
  477. xmlHttp.open(opts.method, url);
  478. } else {
  479. // very old browser, not supported
  480. console.log('your browser is too old to support CORS requests');
  481. }
  482. xmlHttp.send(body);
  483. xmlHttp.onload = function(event) {
  484. if (!self._isUndefined(event) && event.target != null) {
  485. var retry = (event.target.status === 0 || event.target.status === 503);
  486. var success = (event.target.status === 200 || event.target.status === 201);
  487. opts.callback(retry, success, event.target, event.target.response != null ? JSON.parse(event.target.response) : null);
  488. } else {
  489. opts.callback(false, true, event, JSON.parse(xmlHttp.responseText));
  490. }
  491. };
  492. xmlHttp.onerror = function() {
  493. opts.callback(true, false, null, { 'message': 'Could not connect to Host'} );
  494. };
  495. },
  496. /*
  497. * Transform search param object in query string
  498. */
  499. _getSearchParams: function(args, params) {
  500. if (this._isUndefined(args) || args == null) {
  501. return params;
  502. }
  503. for (var key in args) {
  504. if (key != null && args.hasOwnProperty(key)) {
  505. params += (params.length === 0) ? '?' : '&';
  506. params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]);
  507. }
  508. }
  509. return params;
  510. },
  511. _isUndefined: function(obj) {
  512. return obj === void 0;
  513. },
  514. /// internal attributes
  515. applicationID: null,
  516. apiKey: null,
  517. hosts: [],
  518. cache: {},
  519. extraHeaders: []
  520. };
  521. /*
  522. * Contains all the functions related to one index
  523. * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object
  524. */
  525. AlgoliaSearch.prototype.Index.prototype = {
  526. /*
  527. * Clear all queries in cache
  528. */
  529. clearCache: function() {
  530. this.cache = {};
  531. },
  532. /*
  533. * Add an object in this index
  534. *
  535. * @param content contains the javascript object to add inside the index
  536. * @param callback (optional) the result callback with two arguments:
  537. * success: boolean set to true if the request was successfull
  538. * content: the server answer that contains 3 elements: createAt, taskId and objectID
  539. * @param objectID (optional) an objectID you want to attribute to this object
  540. * (if the attribute already exist the old object will be overwrite)
  541. */
  542. addObject: function(content, callback, objectID) {
  543. var indexObj = this;
  544. if (this.as._isUndefined(objectID)) {
  545. this.as._jsonRequest({ method: 'POST',
  546. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName),
  547. body: content,
  548. callback: callback });
  549. } else {
  550. this.as._jsonRequest({ method: 'PUT',
  551. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
  552. body: content,
  553. callback: callback });
  554. }
  555. },
  556. /*
  557. * Add several objects
  558. *
  559. * @param objects contains an array of objects to add
  560. * @param callback (optional) the result callback with two arguments:
  561. * success: boolean set to true if the request was successfull
  562. * content: the server answer that updateAt and taskID
  563. */
  564. addObjects: function(objects, callback) {
  565. var indexObj = this;
  566. var postObj = {requests:[]};
  567. for (var i = 0; i < objects.length; ++i) {
  568. var request = { action: 'addObject',
  569. body: objects[i] };
  570. postObj.requests.push(request);
  571. }
  572. this.as._jsonRequest({ method: 'POST',
  573. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
  574. body: postObj,
  575. callback: callback });
  576. },
  577. /*
  578. * Get an object from this index
  579. *
  580. * @param objectID the unique identifier of the object to retrieve
  581. * @param callback (optional) the result callback with two arguments
  582. * success: boolean set to true if the request was successfull
  583. * content: the object to retrieve or the error message if a failure occured
  584. * @param attributes (optional) if set, contains the array of attribute names to retrieve
  585. */
  586. getObject: function(objectID, callback, attributes) {
  587. var indexObj = this;
  588. var params = '';
  589. if (!this.as._isUndefined(attributes)) {
  590. params = '?attributes=';
  591. for (var i = 0; i < attributes.length; ++i) {
  592. if (i !== 0) {
  593. params += ',';
  594. }
  595. params += attributes[i];
  596. }
  597. }
  598. this.as._jsonRequest({ method: 'GET',
  599. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params,
  600. callback: callback });
  601. },
  602. /*
  603. * Update partially an object (only update attributes passed in argument)
  604. *
  605. * @param partialObject contains the javascript attributes to override, the
  606. * object must contains an objectID attribute
  607. * @param callback (optional) the result callback with two arguments:
  608. * success: boolean set to true if the request was successfull
  609. * content: the server answer that contains 3 elements: createAt, taskId and objectID
  610. */
  611. partialUpdateObject: function(partialObject, callback) {
  612. var indexObj = this;
  613. this.as._jsonRequest({ method: 'POST',
  614. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial',
  615. body: partialObject,
  616. callback: callback });
  617. },
  618. /*
  619. * Partially Override the content of several objects
  620. *
  621. * @param objects contains an array of objects to update (each object must contains a objectID attribute)
  622. * @param callback (optional) the result callback with two arguments:
  623. * success: boolean set to true if the request was successfull
  624. * content: the server answer that updateAt and taskID
  625. */
  626. partialUpdateObjects: function(objects, callback) {
  627. var indexObj = this;
  628. var postObj = {requests:[]};
  629. for (var i = 0; i < objects.length; ++i) {
  630. var request = { action: 'partialUpdateObject',
  631. objectID: objects[i].objectID,
  632. body: objects[i] };
  633. postObj.requests.push(request);
  634. }
  635. this.as._jsonRequest({ method: 'POST',
  636. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
  637. body: postObj,
  638. callback: callback });
  639. },
  640. /*
  641. * Override the content of object
  642. *
  643. * @param object contains the javascript object to save, the object must contains an objectID attribute
  644. * @param callback (optional) the result callback with two arguments:
  645. * success: boolean set to true if the request was successfull
  646. * content: the server answer that updateAt and taskID
  647. */
  648. saveObject: function(object, callback) {
  649. var indexObj = this;
  650. this.as._jsonRequest({ method: 'PUT',
  651. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID),
  652. body: object,
  653. callback: callback });
  654. },
  655. /*
  656. * Override the content of several objects
  657. *
  658. * @param objects contains an array of objects to update (each object must contains a objectID attribute)
  659. * @param callback (optional) the result callback with two arguments:
  660. * success: boolean set to true if the request was successfull
  661. * content: the server answer that updateAt and taskID
  662. */
  663. saveObjects: function(objects, callback) {
  664. var indexObj = this;
  665. var postObj = {requests:[]};
  666. for (var i = 0; i < objects.length; ++i) {
  667. var request = { action: 'updateObject',
  668. objectID: objects[i].objectID,
  669. body: objects[i] };
  670. postObj.requests.push(request);
  671. }
  672. this.as._jsonRequest({ method: 'POST',
  673. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
  674. body: postObj,
  675. callback: callback });
  676. },
  677. /*
  678. * Delete an object from the index
  679. *
  680. * @param objectID the unique identifier of object to delete
  681. * @param callback (optional) the result callback with two arguments:
  682. * success: boolean set to true if the request was successfull
  683. * content: the server answer that contains 3 elements: createAt, taskId and objectID
  684. */
  685. deleteObject: function(objectID, callback) {
  686. if (objectID == null || objectID.length === 0) {
  687. callback(false, { message: 'empty objectID'});
  688. return;
  689. }
  690. var indexObj = this;
  691. this.as._jsonRequest({ method: 'DELETE',
  692. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
  693. callback: callback });
  694. },
  695. /*
  696. * Search inside the index using XMLHttpRequest request (Using a POST query to
  697. * minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
  698. *
  699. * @param query the full text query
  700. * @param callback the result callback with two arguments:
  701. * success: boolean set to true if the request was successfull. If false, the content contains the error.
  702. * content: the server answer that contains the list of results.
  703. * @param args (optional) if set, contains an object with query parameters:
  704. * - page: (integer) Pagination parameter used to select the page to retrieve.
  705. * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
  706. * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20.
  707. * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size).
  708. * Attributes are separated with a comma (for example "name,address").
  709. * You can also use a string array encoding (for example ["name","address"]).
  710. * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index.
  711. * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query.
  712. * Attributes are separated by a comma. You can also use a string array encoding (for example ["name","address"]).
  713. * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted.
  714. * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted.
  715. * A matchLevel is returned for each highlighted attribute and can contain:
  716. * - full: if all the query terms were found in the attribute,
  717. * - partial: if only some of the query terms were found,
  718. * - none: if none of the query terms were found.
  719. * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`).
  720. * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10).
  721. * You can also use a string array encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is computed.
  722. * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3.
  723. * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7.
  724. * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute.
  725. * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma).
  726. * For example aroundLatLng=47.316669,5.016670).
  727. * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision
  728. * (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).
  729. * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
  730. * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng).
  731. * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201).
  732. * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
  733. * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma.
  734. * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`.
  735. * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000.
  736. * You can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]).
  737. * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas.
  738. * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3).
  739. * You can also use a string array encoding, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3).
  740. * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}).
  741. * - facetFilters: filter the query by a list of facets.
  742. * Facets are separated by commas and each facet is encoded as `attributeName:value`.
  743. * For example: `facetFilters=category:Book,author:John%20Doe`.
  744. * You can also use a string array encoding (for example `["category:Book","author:John%20Doe"]`).
  745. * - facets: List of object attributes that you want to use for faceting.
  746. * Attributes are separated with a comma (for example `"category,author"` ).
  747. * You can also use a JSON string array encoding (for example ["category","author"]).
  748. * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter.
  749. * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**.
  750. * - queryType: select how the query words are interpreted, it can be one of the following value:
  751. * - prefixAll: all query words are interpreted as prefixes,
  752. * - prefixLast: only the last word is interpreted as a prefix (default behavior),
  753. * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
  754. * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query.
  755. * The list of words is comma separated.
  756. * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set.
  757. * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter,
  758. * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results.
  759. * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best
  760. * one is kept and others are removed.
  761. * @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime.
  762. */
  763. search: function(query, callback, args, delay) {
  764. var indexObj = this;
  765. var params = 'query=' + encodeURIComponent(query);
  766. if (!this.as._isUndefined(args) && args != null) {
  767. params = this.as._getSearchParams(args, params);
  768. }
  769. window.clearTimeout(indexObj.onDelayTrigger);
  770. if (!this.as._isUndefined(delay) && delay != null && delay > 0) {
  771. var onDelayTrigger = window.setTimeout( function() {
  772. indexObj._search(params, callback);
  773. }, delay);
  774. indexObj.onDelayTrigger = onDelayTrigger;
  775. } else {
  776. this._search(params, callback);
  777. }
  778. },
  779. /*
  780. * Browse all index content
  781. *
  782. * @param page Pagination parameter used to select the page to retrieve.
  783. * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
  784. * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000.
  785. */
  786. browse: function(page, callback, hitsPerPage) {
  787. var indexObj = this;
  788. var params = '?page=' + page;
  789. if (!_.isUndefined(hitsPerPage)) {
  790. params += '&hitsPerPage=' + hitsPerPage;
  791. }
  792. this.as._jsonRequest({ method: 'GET',
  793. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params,
  794. callback: callback });
  795. },
  796. /*
  797. * Get a Typeahead.js adapter
  798. * @param searchParams contains an object with query parameters (see search for details)
  799. */
  800. ttAdapter: function(params) {
  801. var self = this;
  802. return function(query, cb) {
  803. self.search(query, function(success, content) {
  804. if (success) {
  805. cb(content.hits);
  806. }
  807. }, params);
  808. };
  809. },
  810. /*
  811. * Wait the publication of a task on the server.
  812. * All server task are asynchronous and you can check with this method that the task is published.
  813. *
  814. * @param taskID the id of the task returned by server
  815. * @param callback the result callback with with two arguments:
  816. * success: boolean set to true if the request was successfull
  817. * content: the server answer that contains the list of results
  818. */
  819. waitTask: function(taskID, callback) {
  820. var indexObj = this;
  821. this.as._jsonRequest({ method: 'GET',
  822. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID,
  823. callback: function(success, body) {
  824. if (success && body.status === 'published') {
  825. callback(true, body);
  826. } else if (success && body.pendingTask) {
  827. return indexObj.waitTask(taskID, callback);
  828. } else {
  829. callback(false, body);
  830. }
  831. }});
  832. },
  833. /*
  834. * This function deletes the index content. Settings and index specific API keys are kept untouched.
  835. *
  836. * @param callback (optional) the result callback with two arguments
  837. * success: boolean set to true if the request was successfull
  838. * content: the settings object or the error message if a failure occured
  839. */
  840. clearIndex: function(callback) {
  841. var indexObj = this;
  842. this.as._jsonRequest({ method: 'POST',
  843. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear',
  844. callback: callback });
  845. },
  846. /*
  847. * Get settings of this index
  848. *
  849. * @param callback (optional) the result callback with two arguments
  850. * success: boolean set to true if the request was successfull
  851. * content: the settings object or the error message if a failure occured
  852. */
  853. getSettings: function(callback) {
  854. var indexObj = this;
  855. this.as._jsonRequest({ method: 'GET',
  856. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
  857. callback: callback });
  858. },
  859. /*
  860. * Set settings for this index
  861. *
  862. * @param settigns the settings object that can contains :
  863. * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3).
  864. * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7).
  865. * - hitsPerPage: (integer) the number of hits per page (default = 10).
  866. * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
  867. * If set to null, all attributes are retrieved.
  868. * - attributesToHighlight: (array of strings) default list of attributes to highlight.
  869. * If set to null, all indexed attributes are highlighted.
  870. * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords).
  871. * By default no snippet is computed. If set to null, no snippet is computed.
  872. * - attributesToIndex: (array of strings) the list of fields you want to index.
  873. * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results.
  874. * This parameter has two important uses:
  875. * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to
  876. * retrieve it but you don't want to search in the base64 string.
  877. * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of
  878. * the list will be considered more important than matches in attributes further down the list.
  879. * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable
  880. * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"].
  881. * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
  882. * 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.
  883. * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled
  884. * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results.
  885. * 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.
  886. * - ranking: (array of strings) controls the way results are sorted.
  887. * We have six available criteria:
  888. * - typo: sort according to number of typos,
  889. * - geo: sort according to decreassing distance when performing a geo-location based search,
  890. * - proximity: sort according to the proximity of query words in hits,
  891. * - attribute: sort according to the order of attributes defined by attributesToIndex,
  892. * - exact:
  893. * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others.
  894. * 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
  895. * show starting by the v letter before it.
  896. * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix).
  897. * - custom: sort according to a user defined formula set in **customRanking** attribute.
  898. * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
  899. * - customRanking: (array of strings) lets you specify part of the ranking.
  900. * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator.
  901. * For example `"customRanking" => ["desc(population)", "asc(name)"]`
  902. * - queryType: Select how the query words are interpreted, it can be one of the following value:
  903. * - prefixAll: all query words are interpreted as prefixes,
  904. * - prefixLast: only the last word is interpreted as a prefix (default behavior),
  905. * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
  906. * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to "<em>").
  907. * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to "</em>").
  908. * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query.
  909. * @param callback (optional) the result callback with two arguments
  910. * success: boolean set to true if the request was successfull
  911. * content: the server answer or the error message if a failure occured
  912. */
  913. setSettings: function(settings, callback) {
  914. var indexObj = this;
  915. this.as._jsonRequest({ method: 'PUT',
  916. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
  917. body: settings,
  918. callback: callback });
  919. },
  920. /*
  921. * List all existing user keys associated to this index
  922. *
  923. * @param callback the result callback with two arguments
  924. * success: boolean set to true if the request was successfull
  925. * content: the server answer with user keys list or error description if success is false.
  926. */
  927. listUserKeys: function(callback) {
  928. var indexObj = this;
  929. this.as._jsonRequest({ method: 'GET',
  930. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
  931. callback: callback });
  932. },
  933. /*
  934. * Get ACL of a user key associated to this index
  935. *
  936. * @param callback the result callback with two arguments
  937. * success: boolean set to true if the request was successfull
  938. * content: the server answer with user keys list or error description if success is false.
  939. */
  940. getUserKeyACL: function(key, callback) {
  941. var indexObj = this;
  942. this.as._jsonRequest({ method: 'GET',
  943. url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
  944. callback: callback });
  945. },
  946. /*
  947. * Delete an existing user key associated to this index
  948. *
  949. * @param callback the result callback with two arguments
  950. * success: boolean set to true if the request was succ