algoliasearch-rails 1.11.17 → 1.11.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +4 -0
  3. data/README.md +48 -6
  4. data/VERSION +1 -1
  5. data/algoliasearch-rails.gemspec +22 -6
  6. data/vendor/assets/javascripts/algolia/algoliasearch.angular.js +1446 -1378
  7. data/vendor/assets/javascripts/algolia/algoliasearch.angular.min.js +2 -2
  8. data/vendor/assets/javascripts/algolia/algoliasearch.jquery.js +1446 -1378
  9. data/vendor/assets/javascripts/algolia/algoliasearch.jquery.min.js +2 -2
  10. data/vendor/assets/javascripts/algolia/algoliasearch.js +1446 -1378
  11. data/vendor/assets/javascripts/algolia/algoliasearch.min.js +2 -2
  12. data/vendor/assets/javascripts/algolia/bloodhound.min.js +7 -0
  13. data/vendor/assets/javascripts/algolia/typeahead.jquery.min.js +7 -0
  14. data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.js +2667 -0
  15. data/vendor/assets/javascripts/algolia/v2/algoliasearch.angular.min.js +7 -0
  16. data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.js +2667 -0
  17. data/vendor/assets/javascripts/algolia/v2/algoliasearch.jquery.min.js +7 -0
  18. data/vendor/assets/javascripts/algolia/v2/algoliasearch.js +2653 -0
  19. data/vendor/assets/javascripts/algolia/v2/algoliasearch.min.js +7 -0
  20. data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.js +1717 -0
  21. data/vendor/assets/javascripts/algolia/v3/algoliasearch.angular.min.js +37 -0
  22. data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.js +1702 -0
  23. data/vendor/assets/javascripts/algolia/v3/algoliasearch.jquery.min.js +37 -0
  24. data/vendor/assets/javascripts/algolia/v3/algoliasearch.js +2732 -0
  25. data/vendor/assets/javascripts/algolia/v3/algoliasearch.min.js +50 -0
  26. metadata +36 -22
@@ -1,7 +1,7 @@
1
1
  /*!
2
- * algoliasearch 2.9.2
2
+ * algoliasearch 2.9.4
3
3
  * https://github.com/algolia/algoliasearch-client-js
4
4
  * Copyright 2014 Algolia SAS; Licensed MIT
5
5
  */
6
6
 
7
- function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f<a.matchedWords.length;++f){var g=a.matchedWords[f];g in b||(b[g]=1,e=!0)}e&&c.push(a.value)}else if("[object Array]"===Object.prototype.toString.call(a))for(var h=0;h<a.length;++h){var i=d(a[h],b);c=c.concat(i)}else if("object"==typeof a)for(var j in a)a.hasOwnProperty(j)&&(c=c.concat(d(a[j],b)));return c}function e(a,b,c){var f=a._highlightResult||a;if(-1===c.indexOf("."))return c in f?d(f[c],b):[];for(var g=c.split("."),h=f,i=0;i<g.length;++i){if("[object Array]"===Object.prototype.toString.call(h)){for(var j=[],k=0;k<h.length;++k)j=j.concat(e(h[k],b,g.slice(i).join(".")));return j}if(!(g[i]in h))return[];h=h[g[i]]}return d(h,b)}var f={},g={},h=e(a,g,b);if(f.title=h.length>0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i<c.length;++i)for(var j=e(a,g,c[i]),k=0;k<j.length;++k)f.subtitles.push({attr:c[i],value:j[k]});return f}var ALGOLIA_VERSION="2.9.2",AlgoliaSearch=function(a,b,c,d,e){var f=this;this.applicationID=a,this.apiKey=b,this.dsn=!0,this.dsnHost=null,this.hosts=[],this.currentHostIndex=0,this.requestTimeoutInMs=2e3,this.extraHeaders=[],this.jsonp=null,this.options={},this.cache={};var g,h="net";if("string"==typeof c)g=c;else{var i=c||{};this.options=i,this._isUndefined(i.method)||(g=i.method),this._isUndefined(i.tld)||(h=i.tld),this._isUndefined(i.dsn)||(this.dsn=i.dsn),this._isUndefined(i.hosts)||(e=i.hosts),this._isUndefined(i.dsnHost)||(this.dsnHost=i.dsnHost),this._isUndefined(i.requestTimeoutInMs)||(this.requestTimeoutInMs=+i.requestTimeoutInMs),this._isUndefined(i.jsonp)||(this.jsonp=i.jsonp)}this._isUndefined(e)&&(e=[this.applicationID+"-1.algolia."+h,this.applicationID+"-2.algolia."+h,this.applicationID+"-3.algolia."+h]),this.host_protocol="http://",this._isUndefined(g)||null===g?this.host_protocol=("https:"==document.location.protocol?"https":"http")+"://":("https"===g||"HTTPS"===g)&&(this.host_protocol="https://");for(var j=0;j<e.length;++j)Math.random()>.5&&this.hosts.reverse(),this.hosts.push(this.host_protocol+e[j]);Math.random()>.5&&this.hosts.reverse(),(this.dsn||null!=this.dsnHost)&&this.hosts.unshift(this.dsnHost?this.host_protocol+this.dsnHost:this.host_protocol+this.applicationID+"-dsn.algolia."+h),this.options.angular&&this.options.angular.$injector.invoke(["$http","$q",function(a,b){f.options.angular.$q=b,f.options.angular.$http=a}])};AlgoliaSearch.prototype={deleteIndex:function(a,b){return this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(a),callback:b})},moveIndex:function(a,b,c){var d={operation:"move",destination:b};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},copyIndex:function(a,b,c){var d={operation:"copy",destination:b};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},getLogs:function(a,b,c){return this._isUndefined(b)&&(b=0),this._isUndefined(c)&&(c=10),this._jsonRequest({method:"GET",url:"/1/logs?offset="+b+"&length="+c,callback:a})},listIndexes:function(a,b){var c="undefined"!=typeof b?"?page="+b:"";return this._jsonRequest({method:"GET",url:"/1/indexes"+c,callback:a})},initIndex:function(a){return new this.Index(this,a)},listUserKeys:function(a){return this._jsonRequest({method:"GET",url:"/1/keys",callback:a})},getUserKeyACL:function(a,b){return this._jsonRequest({method:"GET",url:"/1/keys/"+a,callback:b})},deleteUserKey:function(a,b){return this._jsonRequest({method:"DELETE",url:"/1/keys/"+a,callback:b})},addUserKey:function(a,b){var c={};return c.acl=a,this._jsonRequest({method:"POST",url:"/1/keys",body:c,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this._jsonRequest({method:"POST",url:"/1/indexes/"+f.indexName+"/keys",body:g,callback:e})},setSecurityTags:function(a){if("[object Array]"===Object.prototype.toString.call(a)){for(var b=[],c=0;c<a.length;++c)if("[object Array]"===Object.prototype.toString.call(a[c])){for(var d=[],e=0;e<a[c].length;++e)d.push(a[c][e]);b.push("("+d.join(",")+")")}else b.push(a[c]);a=b.join(",")}this.tagFilters=a},setUserToken:function(a){this.userToken=a},startQueriesBatch:function(){this.batch=[]},addQueryInBatch:function(a,b,c){var d="query="+encodeURIComponent(b);this._isUndefined(c)||null===c||(d=this._getSearchParams(c,d)),this.batch.push({indexName:a,params:d})},clearCache:function(){this.cache={}},sendQueriesBatch:function(a,b){for(var c=this,d={requests:[]},e=0;e<c.batch.length;++e)d.requests.push(c.batch[e]);if(window.clearTimeout(c.onDelayTrigger),this._isUndefined(b)||null===b||!(b>0))return this._sendQueriesBatch(d,a);var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f},setRequestTimeout:function(a){a&&(this.requestTimeoutInMs=parseInt(a,10))},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null===this.jsonp){var c=this;return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:function(d,e){d?(c.jsonp=!1,b&&b(d,e)):(c.jsonp=!0,c._sendQueriesBatch(a,b))}})}if(this.jsonp){for(var d="",e=0;e<a.requests.length;++e){var f="/1/indexes/"+encodeURIComponent(a.requests[e].indexName)+"?"+a.requests[e].params;d+=e+"="+encodeURIComponent(f)+"&"}var g={params:d};return this._jsonRequest({cache:this.cache,method:"GET",url:"/1/indexes/*",body:g,callback:b})}return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:b})},_jsonRequest:function(a){var b=this,c=a.callback,d=null,e=a.url,f=null;if(this.options.jQuery?(f=this.options.jQuery.$.Deferred(),f.promise=f.promise()):this.options.angular&&(f=this.options.angular.$q.defer()),this._isUndefined(a.body)||(e=a.url+"_body_"+JSON.stringify(a.body)),!this._isUndefined(a.cache)&&(d=a.cache,!this._isUndefined(d[e])))return!this._isUndefined(c)&&c&&setTimeout(function(){c(!0,d[e])},1),f&&f.resolve(d[e]),f&&f.promise;a.successiveRetryCount=0;var g=function(){if(a.successiveRetryCount>=b.hosts.length){var h={message:"Cannot connect the Algolia's Search API. Please send an email to support@algolia.com to report the issue."};return!b._isUndefined(c)&&c&&(a.successiveRetryCount=0,c(!1,h)),void(f&&f.reject(h))}a.callback=function(h,i,j,k){i&&!b._isUndefined(a.cache)&&(d[e]=k),!i&&h?(b.currentHostIndex=++b.currentHostIndex%b.hosts.length,a.successiveRetryCount+=1,g()):(a.successiveRetryCount=0,f&&(i?f.resolve(k):f.reject(k)),!b._isUndefined(c)&&c&&c(i,k))},a.hostname=b.hosts[b.currentHostIndex],b._jsonRequestByHost(a)};return g(),f&&f.promise},_jsonRequestByHost:function(a){var b=a.hostname+a.url;this.jsonp?this._makeJsonpRequestByHost(b,a):this.options.jQuery?this._makejQueryRequestByHost(b,a):this.options.angular?this._makeAngularRequestByHost(b,a):this._makeXmlHttpRequestByHost(b,a)},_makeAngularRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var d=0;d<this.extraHeaders.length;++d)a+="&"+this.extraHeaders[d].key+"="+this.extraHeaders[d].value;this.options.angular.$http({url:a,method:b.method,data:c,cache:!1,timeout:this.requestTimeoutInMs*(b.successiveRetryCount+1)}).then(function(a){b.callback(!1,!0,null,a.data)},function(a){0===a.status?b.callback(!0,!1,null,a.data):400==a.status||403===a.status||404===a.status?b.callback(!1,!1,null,a.data):b.callback(!0,!1,null,a.data)})},_makejQueryRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var d=0;d<this.extraHeaders.length;++d)a+="&"+this.extraHeaders[d].key+"="+this.extraHeaders[d].value;this.options.jQuery.$.ajax(a,{type:b.method,timeout:this.requestTimeoutInMs*(b.successiveRetryCount+1),dataType:"json",data:c,error:function(c,d,e){"timeout"===d?b.callback(!0,!1,null,{message:"Timeout - Could not connect to endpoint "+a}):400===c.status||403===c.status||404===c.status?b.callback(!1,!1,null,c.responseJSON):b.callback(!0,!1,null,{message:e})},success:function(a){b.callback(!1,!0,null,a)}})},_makeJsonpRequestByHost:function(a,b){if("GET"!==b.method)return void b.callback(!0,!1,null,{message:"Method "+b.method+" "+a+" is not supported by JSONP."});this.jsonpCounter=this.jsonpCounter||0,this.jsonpCounter+=1;var c=document.getElementsByTagName("head")[0],d=document.createElement("script"),e="algoliaJSONP_"+this.jsonpCounter,f=!1,g=null;window[e]=function(a){b.callback(!1,!0,null,a);try{delete window[e]}catch(c){window[e]=void 0}},d.type="text/javascript",d.src=a+"?callback="+e+"&X-Algolia-Application-Id="+this.applicationID+"&X-Algolia-API-Key="+this.apiKey,this.tagFilters&&(d.src+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters)),this.userToken&&(d.src+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken));for(var h=0;h<this.extraHeaders.length;++h)d.src+="&"+this.extraHeaders[h].key+"="+this.extraHeaders[h].value;b.body&&b.body.params&&(d.src+="&"+b.body.params),g=setTimeout(function(){d.onload=d.onreadystatechange=d.onerror=null,window[e]=function(){try{delete window[e]}catch(a){window[e]=void 0}},b.callback(!0,!1,null,{message:"Timeout - Failed to load JSONP script."}),c.removeChild(d),clearTimeout(g),g=null},this.requestTimeoutInMs),d.onload=d.onreadystatechange=function(){if(clearTimeout(g),g=null,!(f||this.readyState&&"loaded"!=this.readyState&&"complete"!=this.readyState)){if(f=!0,"undefined"==typeof window[e+"_loaded"]){b.callback(!0,!1,null,{message:"Failed to load JSONP script."});try{delete window[e]}catch(a){window[e]=void 0}}else try{delete window[e+"_loaded"]}catch(a){window[e+"_loaded"]=void 0}d.onload=d.onreadystatechange=null,c.removeChild(d)}},d.onerror=function(){clearTimeout(g),g=null,b.callback(!0,!1,null,{message:"Failed to load JSONP script."}),c.removeChild(d);try{delete window[e]}catch(a){window[e]=void 0}},c.appendChild(d)},_makeXmlHttpRequestByHost:function(a,b){var c=this,d=window.XMLHttpRequest?new XMLHttpRequest:{},e=null,f=null;this._isUndefined(b.body)||(e=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var g=0;g<this.extraHeaders.length;++g)a+="&"+this.extraHeaders[g].key+"="+this.extraHeaders[g].value;if("withCredentials"in d)d.open(b.method,a,!0),d.timeout=this.requestTimeoutInMs*(b.successiveRetryCount+1),null!==e&&d.setRequestHeader("Content-type","application/x-www-form-urlencoded");else{if("undefined"==typeof XDomainRequest)return void b.callback(!1,!1,null,{message:"CORS not supported"});d=new XDomainRequest,d.open(b.method,a)}f=setTimeout(function(){return d.abort(),d.aborted===!0?void stopLoadAnimation():(b.callback(!0,!1,null,{message:"Timeout - Could not connect to endpoint "+a}),clearTimeout(f),void(f=null))},this.requestTimeoutInMs*(b.successiveRetryCount+1)),d.onload=function(a){if(clearTimeout(f),f=null,c._isUndefined(a)||null===a.target)b.callback(!1,!0,a,JSON.parse(d.responseText));else{var e=!1,g=null;"undefined"!=typeof XDomainRequest?(g=a.target.responseText,e=g&&g.length>0):(g=a.target.response,e=200===a.target.status||201===a.target.status);var h=!e&&400!==a.target.status&&403!==a.target.status&&404!==a.target.status;b.callback(h,e,a.target,g?JSON.parse(g):null)}},d.ontimeout=function(){},d.onerror=function(a){clearTimeout(f),f=null,b.callback(!0,!1,null,{message:"Could not connect to host",error:a})},d.send(e)},_getSearchParams:function(a,b){if(this._isUndefined(a)||null===a)return b;for(var c in a)null!==c&&a.hasOwnProperty(c)&&(b+=0===b.length?"?":"&",b+=c+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(a[c])?JSON.stringify(a[c]):a[c]));return b},_isUndefined:function(a){return void 0===a},applicationID:null,apiKey:null,tagFilters:null,userToken:null,hosts:[],extraHeaders:[]},AlgoliaSearch.prototype.Index.prototype={clearCache:function(){this.cache={}},addObject:function(a,b,c){var d=this;return this.as._jsonRequest(this.as._isUndefined(c)?{method:"POST",url:"/1/indexes/"+encodeURIComponent(d.indexName),body:a,callback:b}:{method:"PUT",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(c),body:a,callback:b})},addObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"addObject",body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},getObject:function(a,b,c){"[object Array]"!==Object.prototype.toString.call(b)||c||(c=b,b=null);var d=this,e="";if(!this.as._isUndefined(c)){e="?attributes=";for(var f=0;f<c.length;++f)0!==f&&(e+=","),e+=c[f]}if(null===this.as.jsonp)return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(a)+e,callback:b});var g={params:e};return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(a),callback:b,body:g})},partialUpdateObject:function(a,b){var c=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a.objectID)+"/partial",body:a,callback:b})},partialUpdateObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"partialUpdateObject",objectID:a[e].objectID,body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},saveObject:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a.objectID),body:a,callback:b})},saveObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"updateObject",objectID:a[e].objectID,body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},deleteObject:function(a,b){if(null===a||0===a.length)return void b(!1,{message:"empty objectID"});var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a),callback:b})},search:function(a,b,c,d){(void 0===a||null===a)&&(a=""),"function"==typeof a&&(b=a,a=""),"object"!=typeof b||!this.as._isUndefined(c)&&c||(c=b,b=null);var e=this,f="query="+encodeURIComponent(a);if(this.as._isUndefined(c)||null===c||(f=this.as._getSearchParams(c,f)),window.clearTimeout(e.onDelayTrigger),this.as._isUndefined(d)||null===d||!(d>0))return this._search(f,b);var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g},browse:function(a,b,c){+b>0&&(this.as._isUndefined(c)||!c)&&(c=b,b=null);var d=this,e="?page="+a;return this.as._isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){a&&d(b.hits)},a)}},waitTask:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};return d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){var c={params:a};if(null===this.as.jsonp){var d=this;return this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:function(c,e){c?(d.as.jsonp=!1,b&&b(c,e)):(d.as.jsonp=!0,d._search(a,b))}})}return this.as._jsonRequest(this.as.jsonp?{cache:this.cache,method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:c,callback:b}:{cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:b})},as:null,indexName:null,typeAheadArgs:null,typeAheadValueOption:null},function(){var a=function(a){a=a||{};for(var b=1;b<arguments.length;b++)if(arguments[b])for(var c in arguments[b])arguments[b].hasOwnProperty(c)&&(a[c]=arguments[b][c]);return a};window.AlgoliaSearchHelper=function(b,c,d){var e={facets:[],disjunctiveFacets:[],hitsPerPage:20,defaultFacetFilters:[]};this.init(b,c,a({},e,d))},AlgoliaSearchHelper.prototype={init:function(a,b,c){this.client=a,this.index=b,this.options=c,this.page=0,this.refinements={},this.excludes={},this.disjunctiveRefinements={},this.extraQueries=[]},search:function(a,b,c){this.q=a,this.searchCallback=b,this.searchParams=c||{},this.page=this.page||0,this.refinements=this.refinements||{},this.disjunctiveRefinements=this.disjunctiveRefinements||{},this._search()},clearRefinements:function(){this.disjunctiveRefinements={},this.refinements={}},addDisjunctiveRefine:function(a,b){this.disjunctiveRefinements=this.disjunctiveRefinements||{},this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{},this.disjunctiveRefinements[a][b]=!0},removeDisjunctiveRefine:function(a,b){this.disjunctiveRefinements=this.disjunctiveRefinements||{},this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{};try{delete this.disjunctiveRefinements[a][b]}catch(c){this.disjunctiveRefinements[a][b]=void 0}},addRefine:function(a,b){var c=a+":"+b;this.refinements=this.refinements||{},this.refinements[c]=!0},removeRefine:function(a,b){var c=a+":"+b;this.refinements=this.refinements||{},this.refinements[c]=!1},addExclude:function(a,b){var c=a+":-"+b;this.excludes=this.excludes||{},this.excludes[c]=!0},removeExclude:function(a,b){var c=a+":-"+b;this.excludes=this.excludes||{},this.excludes[c]=!1},toggleExclude:function(a,b){for(var c=0;c<this.options.facets.length;++c)if(this.options.facets[c]==a){var d=a+":-"+b;return this.excludes[d]=!this.excludes[d],this.page=0,this._search(),!0}return!1},toggleRefine:function(a,b){for(var c=0;c<this.options.facets.length;++c)if(this.options.facets[c]==a){var d=a+":"+b;return this.refinements[d]=!this.refinements[d],this.page=0,this._search(),!0}this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{};for(var e=0;e<this.options.disjunctiveFacets.length;++e)if(this.options.disjunctiveFacets[e]==a)return this.disjunctiveRefinements[a][b]=!this.disjunctiveRefinements[a][b],this.page=0,this._search(),!0;return!1},isRefined:function(a,b){var c=a+":"+b;return this.refinements[c]?!0:this.disjunctiveRefinements[a]&&this.disjunctiveRefinements[a][b]?!0:!1},isExcluded:function(a,b){var c=a+":-"+b;return this.excludes[c]?!0:!1},nextPage:function(){this._gotoPage(this.page+1)},previousPage:function(){this.page>0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},setIndex:function(a){this.index=a},getIndex:function(){return this.index},clearExtraQueries:function(){this.extraQueries=[]},addExtraQuery:function(a,b,c){this.extraQueries.push({index:a,query:b,params:c||{}})},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());var a=[],b={},c=0;for(c=0;c<this.options.disjunctiveFacets.length;++c){var d=this.options.disjunctiveFacets[c];this._hasDisjunctiveRefinements(d)?a.push(d):b[d]=!0}for(c=0;c<a.length;++c)this.client.addQueryInBatch(this.index,this.q,this._getDisjunctiveFacetSearchParams(a[c]));for(c=0;c<this.extraQueries.length;++c)this.client.addQueryInBatch(this.extraQueries[c].index,this.extraQueries[c].query,this.extraQueries[c].params);var e=this;this.client.sendQueriesBatch(function(d,f){if(!d)return void e.searchCallback(!1,f);var g=f.results[0];g.disjunctiveFacets=g.disjunctiveFacets||{},g.facetStats=g.facetStats||{};for(var h in b)if(g.facets[h]&&!g.disjunctiveFacets[h]){g.disjunctiveFacets[h]=g.facets[h];try{delete g.facets[h]}catch(i){g.facets[h]=void 0}}for(c=0;c<a.length;++c){for(var j in f.results[c+1].facets)if(g.disjunctiveFacets[j]=f.results[c+1].facets[j],e.disjunctiveRefinements[j])for(var k in e.disjunctiveRefinements[j])!g.disjunctiveFacets[j][k]&&e.disjunctiveRefinements[j][k]&&(g.disjunctiveFacets[j][k]=0);for(var l in f.results[c+1].facets_stats)g.facetStats[l]=f.results[c+1].facets_stats[l]}for(var m in e.excludes)if(e.excludes[m]){var i=m.indexOf(":-"),h=m.slice(0,i),k=m.slice(i+2);g.facets[h]=g.facets[h]||{},g.facets[h][k]||(g.facets[h][k]=0)}if(0===e.extraQueries.length)e.searchCallback(!0,g);else{var n={results:[g]};for(c=0;c<e.extraQueries.length;++c)n.results.push(f.results[1+a.length+c]);e.searchCallback(!0,n)}})},_getHitsSearchParams:function(){var b=[],c=0;for(c=0;c<this.options.facets.length;++c)b.push(this.options.facets[c]);for(c=0;c<this.options.disjunctiveFacets.length;++c){var d=this.options.disjunctiveFacets[c];this._hasDisjunctiveRefinements(d)||b.push(d)}return a({},{hitsPerPage:this.options.hitsPerPage,page:this.page,facets:b,facetFilters:this._getFacetFilters()},this.searchParams)},_getDisjunctiveFacetSearchParams:function(b){return a({},this.searchParams,{hitsPerPage:1,page:0,attributesToRetrieve:[],attributesToHighlight:[],attributesToSnippet:[],facets:b,facetFilters:this._getFacetFilters(b)})},_hasDisjunctiveRefinements:function(a){for(var b in this.disjunctiveRefinements[a])if(this.disjunctiveRefinements[a][b])return!0;return!1},_getFacetFilters:function(a){var b=[];if(this.options.defaultFacetFilters)for(var c=0;c<this.options.defaultFacetFilters.length;++c)b.push(this.options.defaultFacetFilters[c]);for(var d in this.refinements)this.refinements[d]&&b.push(d);for(var d in this.excludes)this.excludes[d]&&b.push(d);for(var e in this.disjunctiveRefinements)if(e!=a){var f=[];for(var g in this.disjunctiveRefinements[e])this.disjunctiveRefinements[e][g]&&f.push(e+":"+g);f.length>0&&b.push(f)}return b}}}(),function(){window.AlgoliaPlaces=function(a,b){this.init(a,b)},AlgoliaPlaces.prototype={init:function(a,b){this.client=new AlgoliaSearch(a,b,"http",!0,["places-1.algolia.io","places-2.algolia.io","places-3.algolia.io"]),this.cache={}},search:function(a,b,c){var d="query="+encodeURIComponent(a);this.client._isUndefined(c)||null==c||(d=this.client._getSearchParams(c,d));var e={params:d,apiKey:this.client.apiKey,appID:this.client.applicationID};this.client._jsonRequest({cache:this.cache,method:"POST",url:"/1/places/query",body:e,callback:b,removeCustomHTTPHeaders:!0})}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),angular.module("algoliasearch",[]).service("algolia",["$injector",function(a){return{Client:function(b,c,d){return d=d||{},d.angular={$injector:a},new AlgoliaSearch(b,c,d)}}}]);
7
+ function AlgoliaExplainResults(a,b,c){function d(a,b){var c=[];if("object"==typeof a&&"matchedWords"in a&&"value"in a){for(var e=!1,f=0;f<a.matchedWords.length;++f){var g=a.matchedWords[f];g in b||(b[g]=1,e=!0)}e&&c.push(a.value)}else if("[object Array]"===Object.prototype.toString.call(a))for(var h=0;h<a.length;++h){var i=d(a[h],b);c=c.concat(i)}else if("object"==typeof a)for(var j in a)a.hasOwnProperty(j)&&(c=c.concat(d(a[j],b)));return c}function e(a,b,c){var f=a._highlightResult||a;if(-1===c.indexOf("."))return c in f?d(f[c],b):[];for(var g=c.split("."),h=f,i=0;i<g.length;++i){if("[object Array]"===Object.prototype.toString.call(h)){for(var j=[],k=0;k<h.length;++k)j=j.concat(e(h[k],b,g.slice(i).join(".")));return j}if(!(g[i]in h))return[];h=h[g[i]]}return d(h,b)}var f={},g={},h=e(a,g,b);if(f.title=h.length>0?h[0]:"",f.subtitles=[],"undefined"!=typeof c)for(var i=0;i<c.length;++i)for(var j=e(a,g,c[i]),k=0;k<j.length;++k)f.subtitles.push({attr:c[i],value:j[k]});return f}var ALGOLIA_VERSION="2.9.4",AlgoliaSearch=function(a,b,c,d,e){var f=this;this.applicationID=a,this.apiKey=b,this.dsn=!0,this.dsnHost=null,this.hosts=[],this.currentHostIndex=0,this.requestTimeoutInMs=2e3,this.extraHeaders=[],this.jsonp=null,this.options={},this.cache={};var g,h="net";if("string"==typeof c)g=c;else{var i=c||{};this.options=i,this._isUndefined(i.method)||(g=i.method),this._isUndefined(i.tld)||(h=i.tld),this._isUndefined(i.dsn)||(this.dsn=i.dsn),this._isUndefined(i.hosts)||(e=i.hosts),this._isUndefined(i.dsnHost)||(this.dsnHost=i.dsnHost),this._isUndefined(i.requestTimeoutInMs)||(this.requestTimeoutInMs=+i.requestTimeoutInMs),this._isUndefined(i.jsonp)||(this.jsonp=i.jsonp)}this._isUndefined(e)&&(e=[this.applicationID+"-1.algolia."+h,this.applicationID+"-2.algolia."+h,this.applicationID+"-3.algolia."+h]),this.host_protocol="http://",this._isUndefined(g)||null===g?this.host_protocol=("https:"==document.location.protocol?"https":"http")+"://":("https"===g||"HTTPS"===g)&&(this.host_protocol="https://");for(var j=0;j<e.length;++j)Math.random()>.5&&this.hosts.reverse(),this.hosts.push(this.host_protocol+e[j]);Math.random()>.5&&this.hosts.reverse(),(this.dsn||null!=this.dsnHost)&&this.hosts.unshift(this.dsnHost?this.host_protocol+this.dsnHost:this.host_protocol+this.applicationID+"-dsn.algolia."+h),this.options.angular&&this.options.angular.$injector.invoke(["$http","$q",function(a,b){f.options.angular.$q=b,f.options.angular.$http=a}])};AlgoliaSearch.JSONPCounter=0,AlgoliaSearch.prototype={deleteIndex:function(a,b){return this._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(a),callback:b})},moveIndex:function(a,b,c){var d={operation:"move",destination:b};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},copyIndex:function(a,b,c){var d={operation:"copy",destination:b};return this._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(a)+"/operation",body:d,callback:c})},getLogs:function(a,b,c){return this._isUndefined(b)&&(b=0),this._isUndefined(c)&&(c=10),this._jsonRequest({method:"GET",url:"/1/logs?offset="+b+"&length="+c,callback:a})},listIndexes:function(a,b){var c="undefined"!=typeof b?"?page="+b:"";return this._jsonRequest({method:"GET",url:"/1/indexes"+c,callback:a})},initIndex:function(a){return new this.Index(this,a)},listUserKeys:function(a){return this._jsonRequest({method:"GET",url:"/1/keys",callback:a})},getUserKeyACL:function(a,b){return this._jsonRequest({method:"GET",url:"/1/keys/"+a,callback:b})},deleteUserKey:function(a,b){return this._jsonRequest({method:"DELETE",url:"/1/keys/"+a,callback:b})},addUserKey:function(a,b){return this.addUserKeyWithValidity(a,0,0,0,b)},addUserKeyWithValidity:function(a,b,c,d,e){var f={};return f.acl=a,f.validity=b,f.maxQueriesPerIPPerHour=c,f.maxHitsPerQuery=d,this._jsonRequest({method:"POST",url:"/1/keys",body:f,callback:e})},setSecurityTags:function(a){if("[object Array]"===Object.prototype.toString.call(a)){for(var b=[],c=0;c<a.length;++c)if("[object Array]"===Object.prototype.toString.call(a[c])){for(var d=[],e=0;e<a[c].length;++e)d.push(a[c][e]);b.push("("+d.join(",")+")")}else b.push(a[c]);a=b.join(",")}this.tagFilters=a},setUserToken:function(a){this.userToken=a},startQueriesBatch:function(){this.batch=[]},addQueryInBatch:function(a,b,c){var d="query="+encodeURIComponent(b);this._isUndefined(c)||null===c||(d=this._getSearchParams(c,d)),this.batch.push({indexName:a,params:d})},clearCache:function(){this.cache={}},sendQueriesBatch:function(a,b){for(var c=this,d={requests:[]},e=0;e<c.batch.length;++e)d.requests.push(c.batch[e]);if(window.clearTimeout(c.onDelayTrigger),this._isUndefined(b)||null===b||!(b>0))return this._sendQueriesBatch(d,a);var f=window.setTimeout(function(){c._sendQueriesBatch(d,a)},b);c.onDelayTrigger=f},setRequestTimeout:function(a){a&&(this.requestTimeoutInMs=parseInt(a,10))},Index:function(a,b){this.indexName=b,this.as=a,this.typeAheadArgs=null,this.typeAheadValueOption=null,this.cache={}},setExtraHeader:function(a,b){this.extraHeaders.push({key:a,value:b})},_sendQueriesBatch:function(a,b){if(null===this.jsonp){var c=this;return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:function(d,e){d?(c.jsonp=!1,b&&b(d,e)):(c.jsonp=!0,c._sendQueriesBatch(a,b))}})}if(this.jsonp){for(var d="",e=0;e<a.requests.length;++e){var f="/1/indexes/"+encodeURIComponent(a.requests[e].indexName)+"?"+a.requests[e].params;d+=e+"="+encodeURIComponent(f)+"&"}var g={params:d};return this._jsonRequest({cache:this.cache,method:"GET",url:"/1/indexes/*",body:g,callback:b})}return this._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/*/queries",body:a,callback:b})},_jsonRequest:function(a){var b=this,c=a.callback,d=null,e=a.url,f=null;if(this.options.jQuery?(f=this.options.jQuery.$.Deferred(),f.promise=f.promise()):this.options.angular&&(f=this.options.angular.$q.defer()),this._isUndefined(a.body)||(e=a.url+"_body_"+JSON.stringify(a.body)),!this._isUndefined(a.cache)&&(d=a.cache,!this._isUndefined(d[e])))return!this._isUndefined(c)&&c&&setTimeout(function(){c(!0,d[e])},1),f&&f.resolve(d[e]),f&&f.promise;a.successiveRetryCount=0;var g=function(){if(a.successiveRetryCount>=b.hosts.length){var h={message:"Cannot connect the Algolia's Search API. Please send an email to support@algolia.com to report the issue."};return!b._isUndefined(c)&&c&&(a.successiveRetryCount=0,c(!1,h)),void(f&&f.reject(h))}a.callback=function(h,i,j){i&&!b._isUndefined(a.cache)&&(d[e]=j),!i&&h?(b.currentHostIndex=++b.currentHostIndex%b.hosts.length,a.successiveRetryCount+=1,g()):(a.successiveRetryCount=0,f&&(i?f.resolve(j):f.reject(j)),!b._isUndefined(c)&&c&&c(i,j))},a.hostname=b.hosts[b.currentHostIndex],b._jsonRequestByHost(a)};return g(),f&&f.promise},_jsonRequestByHost:function(a){var b=a.hostname+a.url;this.jsonp?this._makeJsonpRequestByHost(b,a):this.options.jQuery?this._makejQueryRequestByHost(b,a):this.options.angular?this._makeAngularRequestByHost(b,a):this._makeXmlHttpRequestByHost(b,a)},_makeAngularRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var d=0;d<this.extraHeaders.length;++d)a+="&"+this.extraHeaders[d].key+"="+this.extraHeaders[d].value;this.options.angular.$http({url:a,method:b.method,data:c,cache:!1,timeout:this.requestTimeoutInMs*(b.successiveRetryCount+1)}).then(function(a){b.callback(!1,!0,a.data)},function(a){0===a.status?b.callback(!0,!1,a.data):400==a.status||403===a.status||404===a.status?b.callback(!1,!1,a.data):b.callback(!0,!1,a.data)})},_makejQueryRequestByHost:function(a,b){var c=null;this._isUndefined(b.body)||(c=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var d=0;d<this.extraHeaders.length;++d)a+="&"+this.extraHeaders[d].key+"="+this.extraHeaders[d].value;this.options.jQuery.$.ajax(a,{type:b.method,timeout:this.requestTimeoutInMs*(b.successiveRetryCount+1),dataType:"json",data:c,error:function(c,d,e){"timeout"===d?b.callback(!0,!1,{message:"Timeout - Could not connect to endpoint "+a}):400===c.status||403===c.status||404===c.status?b.callback(!1,!1,c.responseJSON):b.callback(!0,!1,{message:e})},success:function(a){b.callback(!1,!0,a)}})},_makeJsonpRequestByHost:function(a,b){if("GET"!==b.method)return void b.callback(!0,!1,{message:"Method "+b.method+" "+a+" is not supported by JSONP."});var c=!1,d=!1;AlgoliaSearch.JSONPCounter+=1;var e,f,g,h=document.getElementsByTagName("head")[0],i=document.createElement("script"),j="algoliaJSONP_"+AlgoliaSearch.JSONPCounter,k=!1;window[j]=function(a){try{delete window[j]}catch(e){window[j]=void 0}if(!d){var f=a&&a.message&&a.status||a&&200,g=200===f,h=!g&&400!==f&&403!==f&&404!==f;c=!0,b.callback(h,g,a)}},i.type="text/javascript",a+="?callback="+j+"&X-Algolia-Application-Id="+this.applicationID+"&X-Algolia-API-Key="+this.apiKey,this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters)),this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken));for(var l=0;l<this.extraHeaders.length;++l)a+="&"+this.extraHeaders[l].key+"="+this.extraHeaders[l].value;b.body&&b.body.params&&(a+="&"+b.body.params),e=setTimeout(function(){d=!0,g(),b.callback(!0,!1,{message:"Timeout - Failed to load JSONP script."})},this.requestTimeoutInMs),f=function(){k||d||(k=!0,g(),c||b.callback(!0,!1,{message:"Failed to load JSONP script."}))},g=function(){clearTimeout(e),i.onload=null,i.onreadystatechange=null,i.onerror=null,h.removeChild(i);try{delete window[j],delete window[j+"_loaded"]}catch(a){window[j]=null,window[j+"_loaded"]=null}},i.onreadystatechange=function(){("loaded"===this.readyState||"complete"===this.readyState)&&f()},i.onload=function(){f()},i.onerror=function(){k||d||(g(),b.callback(!0,!1,{message:"Failed to load JSONP script."}))},i.async=!0,i.defer=!0,i.src=a,h.appendChild(i)},_makeXmlHttpRequestByHost:function(a,b){if(!this._support.cors&&!this._support.hasXDomainRequest)return void b.callback(!1,!1,{message:"CORS not supported"});var c,d,e,f=null,g=this._support.cors?new XMLHttpRequest:new XDomainRequest,h=this;this._isUndefined(b.body)||(f=JSON.stringify(b.body)),a+=(-1===a.indexOf("?")?"?":"&")+"X-Algolia-API-Key="+this.apiKey,a+="&X-Algolia-Application-Id="+this.applicationID,this.userToken&&(a+="&X-Algolia-UserToken="+encodeURIComponent(this.userToken)),this.tagFilters&&(a+="&X-Algolia-TagFilters="+encodeURIComponent(this.tagFilters));for(var i=0;i<this.extraHeaders.length;++i)a+="&"+this.extraHeaders[i].key+"="+this.extraHeaders[i].value;e=function(){h._support.timeout||(d=!0,g.abort()),b.callback(!0,!1,{message:"Timeout - Could not connect to endpoint "+a})},g instanceof XMLHttpRequest?g.open(b.method,a,!0):g.open(b.method,a),this._support.cors&&null!==f&&"GET"!==b.method&&g.setRequestHeader("Content-type","application/x-www-form-urlencoded"),g.onload=function(){if(!d){h._support.timeout||clearTimeout(c);var a=null;try{a=JSON.parse(g.responseText)}catch(e){}var f=g.status||a&&a.message&&a.status||a&&200,i=200===f||201===f,j=!i&&400!==f&&403!==f&&404!==f;b.callback(j,i,a)}},this._support.timeout?(g.timeout=this.requestTimeoutInMs*(b.successiveRetryCount+1),g.ontimeout=e):c=setTimeout(e,this.requestTimeoutInMs*(b.successiveRetryCount+1)),g.onerror=function(a){d||(h._support.timeout||clearTimeout(c),b.callback(!0,!1,{message:"Could not connect to host",error:a}))},g.send(f)},_getSearchParams:function(a,b){if(this._isUndefined(a)||null===a)return b;for(var c in a)null!==c&&a.hasOwnProperty(c)&&(b+=0===b.length?"?":"&",b+=c+"="+encodeURIComponent("[object Array]"===Object.prototype.toString.call(a[c])?JSON.stringify(a[c]):a[c]));return b},_isUndefined:function(a){return void 0===a},_support:{hasXMLHttpRequest:"XMLHttpRequest"in window,hasXDomainRequest:"XDomainRequest"in window,cors:"withCredentials"in new XMLHttpRequest,timeout:"timeout"in new XMLHttpRequest}},AlgoliaSearch.prototype.Index.prototype={clearCache:function(){this.cache={}},addObject:function(a,b,c){var d=this;return this.as._jsonRequest(this.as._isUndefined(c)?{method:"POST",url:"/1/indexes/"+encodeURIComponent(d.indexName),body:a,callback:b}:{method:"PUT",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(c),body:a,callback:b})},addObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"addObject",body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},getObject:function(a,b,c){"[object Array]"!==Object.prototype.toString.call(b)||c||(c=b,b=null);var d=this,e="";if(!this.as._isUndefined(c)){e="?attributes=";for(var f=0;f<c.length;++f)0!==f&&(e+=","),e+=c[f]}return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/"+encodeURIComponent(a)+e,callback:b})},partialUpdateObject:function(a,b){var c=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a.objectID)+"/partial",body:a,callback:b})},partialUpdateObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"partialUpdateObject",objectID:a[e].objectID,body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},saveObject:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a.objectID),body:a,callback:b})},saveObjects:function(a,b){for(var c=this,d={requests:[]},e=0;e<a.length;++e){var f={action:"updateObject",objectID:a[e].objectID,body:a[e]};d.requests.push(f)}return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/batch",body:d,callback:b})},deleteObject:function(a,b){if(null===a||0===a.length)return void b(!1,{message:"empty objectID"});var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/"+encodeURIComponent(a),callback:b})},search:function(a,b,c,d){(void 0===a||null===a)&&(a=""),"function"==typeof a&&(b=a,a=""),"object"!=typeof b||!this.as._isUndefined(c)&&c||(c=b,b=null);var e=this,f="query="+encodeURIComponent(a);if(this.as._isUndefined(c)||null===c||(f=this.as._getSearchParams(c,f)),window.clearTimeout(e.onDelayTrigger),this.as._isUndefined(d)||null===d||!(d>0))return this._search(f,b);var g=window.setTimeout(function(){e._search(f,b)},d);e.onDelayTrigger=g},browse:function(a,b,c){+b>0&&(this.as._isUndefined(c)||!c)&&(c=b,b=null);var d=this,e="?page="+a;return this.as._isUndefined(c)||(e+="&hitsPerPage="+c),this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(d.indexName)+"/browse"+e,callback:b})},ttAdapter:function(a){var b=this;return function(c,d){b.search(c,function(a,b){d(a?b.hits:b&&b.message)},a)}},waitTask:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/task/"+a,callback:function(d,e){d?"published"===e.status?b(!0,e):setTimeout(function(){c.waitTask(a,b)},100):b(!1,e)}})},clearIndex:function(a){var b=this;return this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/clear",callback:a})},getSettings:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/settings",callback:a})},setSettings:function(a,b){var c=this;return this.as._jsonRequest({method:"PUT",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/settings",body:a,callback:b})},listUserKeys:function(a){var b=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(b.indexName)+"/keys",callback:a})},getUserKeyACL:function(a,b){var c=this;return this.as._jsonRequest({method:"GET",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},deleteUserKey:function(a,b){var c=this;return this.as._jsonRequest({method:"DELETE",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys/"+a,callback:b})},addUserKey:function(a,b){var c=this,d={};return d.acl=a,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(c.indexName)+"/keys",body:d,callback:b})},addUserKeyWithValidity:function(a,b,c,d,e){var f=this,g={};return g.acl=a,g.validity=b,g.maxQueriesPerIPPerHour=c,g.maxHitsPerQuery=d,this.as._jsonRequest({method:"POST",url:"/1/indexes/"+encodeURIComponent(f.indexName)+"/keys",body:g,callback:e})},_search:function(a,b){var c={params:a};if(null===this.as.jsonp){var d=this;return this.as._jsonRequest({cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:function(c,e){var f=e&&e.status;c||f&&4===Math.floor(f/100)||1===Math.floor(f/100)?(d.as.jsonp=!1,b&&b(c,e)):(d.as.jsonp=!0,d._search(a,b))}})}return this.as._jsonRequest(this.as.jsonp?{cache:this.cache,method:"GET",url:"/1/indexes/"+encodeURIComponent(this.indexName),body:c,callback:b}:{cache:this.cache,method:"POST",url:"/1/indexes/"+encodeURIComponent(this.indexName)+"/query",body:c,callback:b})},as:null,indexName:null,typeAheadArgs:null,typeAheadValueOption:null},function(){var a=function(a){a=a||{};for(var b=1;b<arguments.length;b++)if(arguments[b])for(var c in arguments[b])arguments[b].hasOwnProperty(c)&&(a[c]=arguments[b][c]);return a};window.AlgoliaSearchHelper=function(b,c,d){var e={facets:[],disjunctiveFacets:[],hitsPerPage:20,defaultFacetFilters:[]};this.init(b,c,a({},e,d))},AlgoliaSearchHelper.prototype={init:function(a,b,c){this.client=a,this.index=b,this.options=c,this.page=0,this.refinements={},this.excludes={},this.disjunctiveRefinements={},this.extraQueries=[]},search:function(a,b,c){this.q=a,this.searchCallback=b,this.searchParams=c||{},this.page=this.page||0,this.refinements=this.refinements||{},this.disjunctiveRefinements=this.disjunctiveRefinements||{},this._search()},clearRefinements:function(){this.disjunctiveRefinements={},this.refinements={}},addDisjunctiveRefine:function(a,b){this.disjunctiveRefinements=this.disjunctiveRefinements||{},this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{},this.disjunctiveRefinements[a][b]=!0},removeDisjunctiveRefine:function(a,b){this.disjunctiveRefinements=this.disjunctiveRefinements||{},this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{};try{delete this.disjunctiveRefinements[a][b]}catch(c){this.disjunctiveRefinements[a][b]=void 0}},addRefine:function(a,b){var c=a+":"+b;this.refinements=this.refinements||{},this.refinements[c]=!0},removeRefine:function(a,b){var c=a+":"+b;this.refinements=this.refinements||{},this.refinements[c]=!1},addExclude:function(a,b){var c=a+":-"+b;this.excludes=this.excludes||{},this.excludes[c]=!0},removeExclude:function(a,b){var c=a+":-"+b;this.excludes=this.excludes||{},this.excludes[c]=!1},toggleExclude:function(a,b){for(var c=0;c<this.options.facets.length;++c)if(this.options.facets[c]==a){var d=a+":-"+b;return this.excludes[d]=!this.excludes[d],this.page=0,this._search(),!0}return!1},toggleRefine:function(a,b){for(var c=0;c<this.options.facets.length;++c)if(this.options.facets[c]==a){var d=a+":"+b;return this.refinements[d]=!this.refinements[d],this.page=0,this._search(),!0}this.disjunctiveRefinements[a]=this.disjunctiveRefinements[a]||{};for(var e=0;e<this.options.disjunctiveFacets.length;++e)if(this.options.disjunctiveFacets[e]==a)return this.disjunctiveRefinements[a][b]=!this.disjunctiveRefinements[a][b],this.page=0,this._search(),!0;return!1},isRefined:function(a,b){var c=a+":"+b;return this.refinements[c]?!0:this.disjunctiveRefinements[a]&&this.disjunctiveRefinements[a][b]?!0:!1},isExcluded:function(a,b){var c=a+":-"+b;return this.excludes[c]?!0:!1},nextPage:function(){this._gotoPage(this.page+1)},previousPage:function(){this.page>0&&this._gotoPage(this.page-1)},gotoPage:function(a){this._gotoPage(a)},setPage:function(a){this.page=a},setIndex:function(a){this.index=a},getIndex:function(){return this.index},clearExtraQueries:function(){this.extraQueries=[]},addExtraQuery:function(a,b,c){this.extraQueries.push({index:a,query:b,params:c||{}})},_gotoPage:function(a){this.page=a,this._search()},_search:function(){this.client.startQueriesBatch(),this.client.addQueryInBatch(this.index,this.q,this._getHitsSearchParams());var a=[],b={},c=0;for(c=0;c<this.options.disjunctiveFacets.length;++c){var d=this.options.disjunctiveFacets[c];this._hasDisjunctiveRefinements(d)?a.push(d):b[d]=!0}for(c=0;c<a.length;++c)this.client.addQueryInBatch(this.index,this.q,this._getDisjunctiveFacetSearchParams(a[c]));for(c=0;c<this.extraQueries.length;++c)this.client.addQueryInBatch(this.extraQueries[c].index,this.extraQueries[c].query,this.extraQueries[c].params);var e=this;this.client.sendQueriesBatch(function(d,f){if(!d)return void e.searchCallback(!1,f);var g=f.results[0];g.disjunctiveFacets=g.disjunctiveFacets||{},g.facets_stats=g.facets_stats||{};for(var h in b)if(g.facets[h]&&!g.disjunctiveFacets[h]){g.disjunctiveFacets[h]=g.facets[h];try{delete g.facets[h]}catch(i){g.facets[h]=void 0}}for(c=0;c<a.length;++c){for(var j in f.results[c+1].facets)if(g.disjunctiveFacets[j]=f.results[c+1].facets[j],e.disjunctiveRefinements[j])for(var k in e.disjunctiveRefinements[j])!g.disjunctiveFacets[j][k]&&e.disjunctiveRefinements[j][k]&&(g.disjunctiveFacets[j][k]=0);for(var l in f.results[c+1].facets_stats)g.facets_stats[l]=f.results[c+1].facets_stats[l]}g.facetStats=g.facets_stats;for(var m in e.excludes)if(e.excludes[m]){var i=m.indexOf(":-"),h=m.slice(0,i),k=m.slice(i+2);g.facets[h]=g.facets[h]||{},g.facets[h][k]||(g.facets[h][k]=0)}if(0===e.extraQueries.length)e.searchCallback(!0,g);else{var n={results:[g]};for(c=0;c<e.extraQueries.length;++c)n.results.push(f.results[1+a.length+c]);e.searchCallback(!0,n)}})},_getHitsSearchParams:function(){var b=[],c=0;for(c=0;c<this.options.facets.length;++c)b.push(this.options.facets[c]);for(c=0;c<this.options.disjunctiveFacets.length;++c){var d=this.options.disjunctiveFacets[c];this._hasDisjunctiveRefinements(d)||b.push(d)}return a({},{hitsPerPage:this.options.hitsPerPage,page:this.page,facets:b,facetFilters:this._getFacetFilters()},this.searchParams)},_getDisjunctiveFacetSearchParams:function(b){return a({},this.searchParams,{hitsPerPage:1,page:0,attributesToRetrieve:[],attributesToHighlight:[],attributesToSnippet:[],facets:b,facetFilters:this._getFacetFilters(b),analytics:!1})},_hasDisjunctiveRefinements:function(a){for(var b in this.disjunctiveRefinements[a])if(this.disjunctiveRefinements[a][b])return!0;return!1},_getFacetFilters:function(a){var b=[];if(this.options.defaultFacetFilters)for(var c=0;c<this.options.defaultFacetFilters.length;++c)b.push(this.options.defaultFacetFilters[c]);for(var d in this.refinements)this.refinements[d]&&b.push(d);for(var d in this.excludes)this.excludes[d]&&b.push(d);for(var e in this.disjunctiveRefinements)if(e!=a){var f=[];for(var g in this.disjunctiveRefinements[e])this.disjunctiveRefinements[e][g]&&f.push(e+":"+g);f.length>0&&b.push(f)}return b}}}(),function(){window.AlgoliaPlaces=function(a,b){this.init(a,b)},AlgoliaPlaces.prototype={init:function(a,b){this.client=new AlgoliaSearch(a,b,"http",!0,["places-1.algolia.io","places-2.algolia.io","places-3.algolia.io"]),this.cache={}},search:function(a,b,c){var d="query="+encodeURIComponent(a);this.client._isUndefined(c)||null==c||(d=this.client._getSearchParams(c,d));var e={params:d,apiKey:this.client.apiKey,appID:this.client.applicationID};this.client._jsonRequest({cache:this.cache,method:"POST",url:"/1/places/query",body:e,callback:b,removeCustomHTTPHeaders:!0})}}}(),"object"!=typeof JSON&&(JSON={}),function(){"use strict";function f(a){return 10>a?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return"string"==typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g,h=gap,i=b[a];switch(i&&"object"==typeof i&&"function"==typeof i.toJSON&&(i=i.toJSON(a)),"function"==typeof rep&&(i=rep.call(b,a,i)),typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";if(gap+=indent,g=[],"[object Array]"===Object.prototype.toString.apply(i)){for(f=i.length,c=0;f>c;c+=1)g[c]=str(c,i)||"null";return e=0===g.length?"[]":gap?"[\n"+gap+g.join(",\n"+gap)+"\n"+h+"]":"["+g.join(",")+"]",gap=h,e}if(rep&&"object"==typeof rep)for(f=rep.length,c=0;f>c;c+=1)"string"==typeof rep[c]&&(d=rep[c],e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&g.push(quote(d)+(gap?": ":":")+e));return e=0===g.length?"{}":gap?"{\n"+gap+g.join(",\n"+gap)+"\n"+h+"}":"{"+g.join(",")+"}",gap=h,e}}"function"!=typeof Date.prototype.toJSON&&(Date.prototype.toJSON=function(){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(){return this.valueOf()});var cx,escapable,gap,indent,meta,rep;"function"!=typeof JSON.stringify&&(escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},JSON.stringify=function(a,b,c){var d;if(gap="",indent="","number"==typeof c)for(d=0;c>d;d+=1)indent+=" ";else"string"==typeof c&&(indent=c);if(rep=b,b&&"function"!=typeof b&&("object"!=typeof b||"number"!=typeof b.length))throw new Error("JSON.stringify");return str("",{"":a})}),"function"!=typeof JSON.parse&&(cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&"object"==typeof e)for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),void 0!==d?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;if(text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})),/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),"function"==typeof reviver?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),angular.module("algoliasearch",[]).service("algolia",["$injector",function(a){return{Client:function(b,c,d){return d=d||{},d.angular={$injector:a},new AlgoliaSearch(b,c,d)}}}]);
@@ -21,7 +21,7 @@
21
21
  * THE SOFTWARE.
22
22
  */
23
23
 
24
- var ALGOLIA_VERSION = '2.9.2';
24
+ var ALGOLIA_VERSION = '2.9.4';
25
25
 
26
26
  /*
27
27
  * Copyright (c) 2013 Algolia
@@ -58,892 +58,959 @@ var ALGOLIA_VERSION = '2.9.2';
58
58
  * - dsnHost (optional) override the automatic computation of dsn hostname
59
59
  */
60
60
  var AlgoliaSearch = function(applicationID, apiKey, methodOrOptions, resolveDNS, hosts) {
61
- var self = this;
62
- this.applicationID = applicationID;
63
- this.apiKey = apiKey;
64
- this.dsn = true;
65
- this.dsnHost = null;
66
- this.hosts = [];
67
- this.currentHostIndex = 0;
68
- this.requestTimeoutInMs = 2000;
69
- this.extraHeaders = [];
70
- this.jsonp = null;
71
- this.options = {};
72
-
73
- // make sure every client instance has it's own cache
74
- this.cache = {};
75
-
76
- var method;
77
- var tld = 'net';
78
- if (typeof methodOrOptions === 'string') { // Old initialization
79
- method = methodOrOptions;
80
- } else {
81
- // Take all option from the hash
82
- var options = methodOrOptions || {};
83
- this.options = options;
84
- if (!this._isUndefined(options.method)) {
85
- method = options.method;
86
- }
87
- if (!this._isUndefined(options.tld)) {
88
- tld = options.tld;
89
- }
90
- if (!this._isUndefined(options.dsn)) {
91
- this.dsn = options.dsn;
92
- }
93
- if (!this._isUndefined(options.hosts)) {
94
- hosts = options.hosts;
95
- }
96
- if (!this._isUndefined(options.dsnHost)) {
97
- this.dsnHost = options.dsnHost;
98
- }
99
- if (!this._isUndefined(options.requestTimeoutInMs)) {
100
- this.requestTimeoutInMs = +options.requestTimeoutInMs;
101
- }
102
- if (!this._isUndefined(options.jsonp)) {
103
- this.jsonp = options.jsonp;
104
- }
61
+ var self = this;
62
+ this.applicationID = applicationID;
63
+ this.apiKey = apiKey;
64
+ this.dsn = true;
65
+ this.dsnHost = null;
66
+ this.hosts = [];
67
+ this.currentHostIndex = 0;
68
+ this.requestTimeoutInMs = 2000;
69
+ this.extraHeaders = [];
70
+ this.jsonp = null;
71
+ this.options = {};
72
+
73
+ // make sure every client instance has it's own cache
74
+ this.cache = {};
75
+
76
+ var method;
77
+ var tld = 'net';
78
+ if (typeof methodOrOptions === 'string') { // Old initialization
79
+ method = methodOrOptions;
80
+ } else {
81
+ // Take all option from the hash
82
+ var options = methodOrOptions || {};
83
+ this.options = options;
84
+ if (!this._isUndefined(options.method)) {
85
+ method = options.method;
105
86
  }
106
- // If hosts is undefined, initialize it with applicationID
107
- if (this._isUndefined(hosts)) {
108
- hosts = [
109
- this.applicationID + '-1.algolia.' + tld,
110
- this.applicationID + '-2.algolia.' + tld,
111
- this.applicationID + '-3.algolia.' + tld
112
- ];
113
- }
114
- // detect is we use http or https
115
- this.host_protocol = 'http://';
116
- if (this._isUndefined(method) || method === null) {
117
- this.host_protocol = ('https:' == document.location.protocol ? 'https' : 'http') + '://';
118
- } else if (method === 'https' || method === 'HTTPS') {
119
- this.host_protocol = 'https://';
120
- }
121
- // Add hosts in random order
122
- for (var i = 0; i < hosts.length; ++i) {
123
- if (Math.random() > 0.5) {
124
- this.hosts.reverse();
125
- }
126
- this.hosts.push(this.host_protocol + hosts[i]);
87
+ if (!this._isUndefined(options.tld)) {
88
+ tld = options.tld;
127
89
  }
128
- if (Math.random() > 0.5) {
129
- this.hosts.reverse();
90
+ if (!this._isUndefined(options.dsn)) {
91
+ this.dsn = options.dsn;
130
92
  }
131
- // then add Distributed Search Network host if there is one
132
- if (this.dsn || this.dsnHost != null) {
133
- if (this.dsnHost) {
134
- this.hosts.unshift(this.host_protocol + this.dsnHost);
135
- } else {
136
- this.hosts.unshift(this.host_protocol + this.applicationID + '-dsn.algolia.' + tld);
137
- }
93
+ if (!this._isUndefined(options.hosts)) {
94
+ hosts = options.hosts;
95
+ }
96
+ if (!this._isUndefined(options.dsnHost)) {
97
+ this.dsnHost = options.dsnHost;
98
+ }
99
+ if (!this._isUndefined(options.requestTimeoutInMs)) {
100
+ this.requestTimeoutInMs = +options.requestTimeoutInMs;
101
+ }
102
+ if (!this._isUndefined(options.jsonp)) {
103
+ this.jsonp = options.jsonp;
138
104
  }
139
- // angular dependencies injection
140
- if (this.options.angular) {
141
- this.options.angular.$injector.invoke(['$http', '$q', function ($http, $q) {
142
- self.options.angular.$q = $q;
143
- self.options.angular.$http = $http;
144
- }]);
105
+ }
106
+ // If hosts is undefined, initialize it with applicationID
107
+ if (this._isUndefined(hosts)) {
108
+ hosts = [
109
+ this.applicationID + '-1.algolia.' + tld,
110
+ this.applicationID + '-2.algolia.' + tld,
111
+ this.applicationID + '-3.algolia.' + tld
112
+ ];
113
+ }
114
+ // detect is we use http or https
115
+ this.host_protocol = 'http://';
116
+ if (this._isUndefined(method) || method === null) {
117
+ this.host_protocol = ('https:' == document.location.protocol ? 'https' : 'http') + '://';
118
+ } else if (method === 'https' || method === 'HTTPS') {
119
+ this.host_protocol = 'https://';
120
+ }
121
+ // Add hosts in random order
122
+ for (var i = 0; i < hosts.length; ++i) {
123
+ if (Math.random() > 0.5) {
124
+ this.hosts.reverse();
125
+ }
126
+ this.hosts.push(this.host_protocol + hosts[i]);
127
+ }
128
+ if (Math.random() > 0.5) {
129
+ this.hosts.reverse();
130
+ }
131
+ // then add Distributed Search Network host if there is one
132
+ if (this.dsn || this.dsnHost != null) {
133
+ if (this.dsnHost) {
134
+ this.hosts.unshift(this.host_protocol + this.dsnHost);
135
+ } else {
136
+ this.hosts.unshift(this.host_protocol + this.applicationID + '-dsn.algolia.' + tld);
145
137
  }
138
+ }
139
+ // angular dependencies injection
140
+ if (this.options.angular) {
141
+ this.options.angular.$injector.invoke(['$http', '$q', function ($http, $q) {
142
+ self.options.angular.$q = $q;
143
+ self.options.angular.$http = $http;
144
+ }]);
145
+ }
146
146
  };
147
147
 
148
+ // This holds the number of JSONP requests done accross clients
149
+ // It's used as part of the ?callback=JSONP_$JSONPCounter when we do JSONP requests
150
+ AlgoliaSearch.JSONPCounter = 0;
151
+
148
152
  function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) {
149
153
 
150
- function _getHitExplanationForOneAttr_recurse(obj, foundWords) {
154
+ function _getHitExplanationForOneAttr_recurse(obj, foundWords) {
155
+ var res = [];
156
+ if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) {
157
+ var match = false;
158
+ for (var j = 0; j < obj.matchedWords.length; ++j) {
159
+ var word = obj.matchedWords[j];
160
+ if (!(word in foundWords)) {
161
+ foundWords[word] = 1;
162
+ match = true;
163
+ }
164
+ }
165
+ if (match) {
166
+ res.push(obj.value);
167
+ }
168
+ } else if (Object.prototype.toString.call(obj) === '[object Array]') {
169
+ for (var i = 0; i < obj.length; ++i) {
170
+ var array = _getHitExplanationForOneAttr_recurse(obj[i], foundWords);
171
+ res = res.concat(array);
172
+ }
173
+ } else if (typeof obj === 'object') {
174
+ for (var prop in obj) {
175
+ if (obj.hasOwnProperty(prop)){
176
+ res = res.concat(_getHitExplanationForOneAttr_recurse(obj[prop], foundWords));
177
+ }
178
+ }
179
+ }
180
+ return res;
181
+ }
182
+
183
+ function _getHitExplanationForOneAttr(hit, foundWords, attr) {
184
+ var base = hit._highlightResult || hit;
185
+ if (attr.indexOf('.') === -1) {
186
+ if (attr in base) {
187
+ return _getHitExplanationForOneAttr_recurse(base[attr], foundWords);
188
+ }
189
+ return [];
190
+ }
191
+ var array = attr.split('.');
192
+ var obj = base;
193
+ for (var i = 0; i < array.length; ++i) {
194
+ if (Object.prototype.toString.call(obj) === '[object Array]') {
151
195
  var res = [];
152
- if (typeof obj === 'object' && 'matchedWords' in obj && 'value' in obj) {
153
- var match = false;
154
- for (var j = 0; j < obj.matchedWords.length; ++j) {
155
- var word = obj.matchedWords[j];
156
- if (!(word in foundWords)) {
157
- foundWords[word] = 1;
158
- match = true;
159
- }
160
- }
161
- if (match) {
162
- res.push(obj.value);
163
- }
164
- } else if (Object.prototype.toString.call(obj) === '[object Array]') {
165
- for (var i = 0; i < obj.length; ++i) {
166
- var array = _getHitExplanationForOneAttr_recurse(obj[i], foundWords);
167
- res = res.concat(array);
168
- }
169
- } else if (typeof obj === 'object') {
170
- for (var prop in obj) {
171
- if (obj.hasOwnProperty(prop)){
172
- res = res.concat(_getHitExplanationForOneAttr_recurse(obj[prop], foundWords));
173
- }
174
- }
196
+ for (var j = 0; j < obj.length; ++j) {
197
+ res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.')));
175
198
  }
176
199
  return res;
200
+ }
201
+ if (array[i] in obj) {
202
+ obj = obj[array[i]];
203
+ } else {
204
+ return [];
205
+ }
206
+ }
207
+ return _getHitExplanationForOneAttr_recurse(obj, foundWords);
208
+ }
209
+
210
+ var res = {};
211
+ var foundWords = {};
212
+ var title = _getHitExplanationForOneAttr(hit, foundWords, titleAttribute);
213
+ res.title = (title.length > 0) ? title[0] : '';
214
+ res.subtitles = [];
215
+
216
+ if (typeof otherAttributes !== 'undefined') {
217
+ for (var i = 0; i < otherAttributes.length; ++i) {
218
+ var attr = _getHitExplanationForOneAttr(hit, foundWords, otherAttributes[i]);
219
+ for (var j = 0; j < attr.length; ++j) {
220
+ res.subtitles.push({ attr: otherAttributes[i], value: attr[j] });
221
+ }
177
222
  }
223
+ }
224
+ return res;
225
+ }
178
226
 
179
- function _getHitExplanationForOneAttr(hit, foundWords, attr) {
180
- var base = hit._highlightResult || hit;
181
- if (attr.indexOf('.') === -1) {
182
- if (attr in base) {
183
- return _getHitExplanationForOneAttr_recurse(base[attr], foundWords);
184
- }
185
- return [];
186
- }
187
- var array = attr.split('.');
188
- var obj = base;
189
- for (var i = 0; i < array.length; ++i) {
190
- if (Object.prototype.toString.call(obj) === '[object Array]') {
191
- var res = [];
192
- for (var j = 0; j < obj.length; ++j) {
193
- res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.')));
194
- }
195
- return res;
196
- }
197
- if (array[i] in obj) {
198
- obj = obj[array[i]];
199
- } else {
200
- return [];
201
- }
202
- }
203
- return _getHitExplanationForOneAttr_recurse(obj, foundWords);
227
+
228
+ AlgoliaSearch.prototype = {
229
+ /*
230
+ * Delete an index
231
+ *
232
+ * @param indexName the name of index to delete
233
+ * @param callback the result callback with two arguments
234
+ * success: boolean set to true if the request was successfull
235
+ * content: the server answer that contains the task ID
236
+ */
237
+ deleteIndex: function(indexName, callback) {
238
+ return this._jsonRequest({ method: 'DELETE',
239
+ url: '/1/indexes/' + encodeURIComponent(indexName),
240
+ callback: callback });
241
+ },
242
+ /**
243
+ * Move an existing index.
244
+ * @param srcIndexName the name of index to copy.
245
+ * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
246
+ * @param callback the result callback with two arguments
247
+ * success: boolean set to true if the request was successfull
248
+ * content: the server answer that contains the task ID
249
+ */
250
+ moveIndex: function(srcIndexName, dstIndexName, callback) {
251
+ var postObj = {operation: 'move', destination: dstIndexName};
252
+ return this._jsonRequest({ method: 'POST',
253
+ url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
254
+ body: postObj,
255
+ callback: callback });
256
+
257
+ },
258
+ /**
259
+ * Copy an existing index.
260
+ * @param srcIndexName the name of index to copy.
261
+ * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
262
+ * @param callback the result callback with two arguments
263
+ * success: boolean set to true if the request was successfull
264
+ * content: the server answer that contains the task ID
265
+ */
266
+ copyIndex: function(srcIndexName, dstIndexName, callback) {
267
+ var postObj = {operation: 'copy', destination: dstIndexName};
268
+ return this._jsonRequest({ method: 'POST',
269
+ url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
270
+ body: postObj,
271
+ callback: callback });
272
+ },
273
+ /**
274
+ * Return last log entries.
275
+ * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
276
+ * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000.
277
+ * @param callback the result callback with two arguments
278
+ * success: boolean set to true if the request was successfull
279
+ * content: the server answer that contains the task ID
280
+ */
281
+ getLogs: function(callback, offset, length) {
282
+ if (this._isUndefined(offset)) {
283
+ offset = 0;
284
+ }
285
+ if (this._isUndefined(length)) {
286
+ length = 10;
204
287
  }
205
288
 
206
- var res = {};
207
- var foundWords = {};
208
- var title = _getHitExplanationForOneAttr(hit, foundWords, titleAttribute);
209
- res.title = (title.length > 0) ? title[0] : '';
210
- res.subtitles = [];
289
+ return this._jsonRequest({ method: 'GET',
290
+ url: '/1/logs?offset=' + offset + '&length=' + length,
291
+ callback: callback });
292
+ },
293
+ /*
294
+ * List all existing indexes (paginated)
295
+ *
296
+ * @param callback the result callback with two arguments
297
+ * success: boolean set to true if the request was successfull
298
+ * content: the server answer with index list or error description if success is false.
299
+ * @param page The page to retrieve, starting at 0.
300
+ */
301
+ listIndexes: function(callback, page) {
302
+ var params = typeof page !== 'undefined' ? '?page=' + page : '';
303
+ return this._jsonRequest({ method: 'GET',
304
+ url: '/1/indexes' + params,
305
+ callback: callback });
306
+ },
307
+
308
+ /*
309
+ * Get the index object initialized
310
+ *
311
+ * @param indexName the name of index
312
+ * @param callback the result callback with one argument (the Index instance)
313
+ */
314
+ initIndex: function(indexName) {
315
+ return new this.Index(this, indexName);
316
+ },
317
+ /*
318
+ * List all existing user keys with their associated ACLs
319
+ *
320
+ * @param callback the result callback with two arguments
321
+ * success: boolean set to true if the request was successfull
322
+ * content: the server answer with user keys list or error description if success is false.
323
+ */
324
+ listUserKeys: function(callback) {
325
+ return this._jsonRequest({ method: 'GET',
326
+ url: '/1/keys',
327
+ callback: callback });
328
+ },
329
+ /*
330
+ * Get ACL of a user key
331
+ *
332
+ * @param callback the result callback with two arguments
333
+ * success: boolean set to true if the request was successfull
334
+ * content: the server answer with user keys list or error description if success is false.
335
+ */
336
+ getUserKeyACL: function(key, callback) {
337
+ return this._jsonRequest({ method: 'GET',
338
+ url: '/1/keys/' + key,
339
+ callback: callback });
340
+ },
341
+ /*
342
+ * Delete an existing user key
343
+ *
344
+ * @param callback the result callback with two arguments
345
+ * success: boolean set to true if the request was successfull
346
+ * content: the server answer with user keys list or error description if success is false.
347
+ */
348
+ deleteUserKey: function(key, callback) {
349
+ return this._jsonRequest({ method: 'DELETE',
350
+ url: '/1/keys/' + key,
351
+ callback: callback });
352
+ },
353
+ /*
354
+ * Add an existing user key
355
+ *
356
+ * @param acls the list of ACL for this key. Defined by an array of strings that
357
+ * can contains the following values:
358
+ * - search: allow to search (https and http)
359
+ * - addObject: allows to add/update an object in the index (https only)
360
+ * - deleteObject : allows to delete an existing object (https only)
361
+ * - deleteIndex : allows to delete index content (https only)
362
+ * - settings : allows to get index settings (https only)
363
+ * - editSettings : allows to change index settings (https only)
364
+ * @param callback the result callback with two arguments
365
+ * success: boolean set to true if the request was successfull
366
+ * content: the server answer with user keys list or error description if success is false.
367
+ */
368
+ addUserKey: function(acls, callback) {
369
+ return this.addUserKeyWithValidity(acls, 0, 0, 0, callback);
370
+ },
371
+ /*
372
+ * Add an existing user key
373
+ *
374
+ * @param acls the list of ACL for this key. Defined by an array of strings that
375
+ * can contains the following values:
376
+ * - search: allow to search (https and http)
377
+ * - addObject: allows to add/update an object in the index (https only)
378
+ * - deleteObject : allows to delete an existing object (https only)
379
+ * - deleteIndex : allows to delete index content (https only)
380
+ * - settings : allows to get index settings (https only)
381
+ * - editSettings : allows to change index settings (https only)
382
+ * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
383
+ * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
384
+ * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
385
+ * @param callback the result callback with two arguments
386
+ * success: boolean set to true if the request was successfull
387
+ * content: the server answer with user keys list or error description if success is false.
388
+ */
389
+ addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
390
+ var aclsObject = {};
391
+ aclsObject.acl = acls;
392
+ aclsObject.validity = validity;
393
+ aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
394
+ aclsObject.maxHitsPerQuery = maxHitsPerQuery;
395
+ return this._jsonRequest({ method: 'POST',
396
+ url: '/1/keys',
397
+ body: aclsObject,
398
+ callback: callback });
399
+ },
211
400
 
212
- if (typeof otherAttributes !== 'undefined') {
213
- for (var i = 0; i < otherAttributes.length; ++i) {
214
- var attr = _getHitExplanationForOneAttr(hit, foundWords, otherAttributes[i]);
215
- for (var j = 0; j < attr.length; ++j) {
216
- res.subtitles.push({ attr: otherAttributes[i], value: attr[j] });
217
- }
401
+ /**
402
+ * Set the extra security tagFilters header
403
+ * @param {string|array} tags The list of tags defining the current security filters
404
+ */
405
+ setSecurityTags: function(tags) {
406
+ if (Object.prototype.toString.call(tags) === '[object Array]') {
407
+ var strTags = [];
408
+ for (var i = 0; i < tags.length; ++i) {
409
+ if (Object.prototype.toString.call(tags[i]) === '[object Array]') {
410
+ var oredTags = [];
411
+ for (var j = 0; j < tags[i].length; ++j) {
412
+ oredTags.push(tags[i][j]);
413
+ }
414
+ strTags.push('(' + oredTags.join(',') + ')');
415
+ } else {
416
+ strTags.push(tags[i]);
218
417
  }
418
+ }
419
+ tags = strTags.join(',');
219
420
  }
220
- return res;
221
- }
421
+ this.tagFilters = tags;
422
+ },
222
423
 
424
+ /**
425
+ * Set the extra user token header
426
+ * @param {string} userToken The token identifying a uniq user (used to apply rate limits)
427
+ */
428
+ setUserToken: function(userToken) {
429
+ this.userToken = userToken;
430
+ },
223
431
 
224
- AlgoliaSearch.prototype = {
225
- /*
226
- * Delete an index
227
- *
228
- * @param indexName the name of index to delete
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 that contains the task ID
232
- */
233
- deleteIndex: function(indexName, callback) {
234
- return this._jsonRequest({ method: 'DELETE',
235
- url: '/1/indexes/' + encodeURIComponent(indexName),
236
- callback: callback });
237
- },
238
- /**
239
- * Move an existing index.
240
- * @param srcIndexName the name of index to copy.
241
- * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
242
- * @param callback the result callback with two arguments
243
- * success: boolean set to true if the request was successfull
244
- * content: the server answer that contains the task ID
245
- */
246
- moveIndex: function(srcIndexName, dstIndexName, callback) {
247
- var postObj = {operation: 'move', destination: dstIndexName};
248
- return this._jsonRequest({ method: 'POST',
249
- url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
250
- body: postObj,
251
- callback: callback });
432
+ /*
433
+ * Initialize a new batch of search queries
434
+ */
435
+ startQueriesBatch: function() {
436
+ this.batch = [];
437
+ },
438
+ /*
439
+ * Add a search query in the batch
440
+ *
441
+ * @param query the full text query
442
+ * @param args (optional) if set, contains an object with query parameters:
443
+ * - attributes: an array of object attribute names to retrieve
444
+ * (if not set all attributes are retrieve)
445
+ * - attributesToHighlight: an array of object attribute names to highlight
446
+ * (if not set indexed attributes are highlighted)
447
+ * - minWordSizefor1Typo: the minimum number of characters to accept one typo.
448
+ * Defaults to 3.
449
+ * - minWordSizefor2Typos: the minimum number of characters to accept two typos.
450
+ * Defaults to 7.
451
+ * - getRankingInfo: if set, the result hits will contain ranking information in
452
+ * _rankingInfo attribute
453
+ * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0.
454
+ * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10.
455
+ */
456
+ addQueryInBatch: function(indexName, query, args) {
457
+ var params = 'query=' + encodeURIComponent(query);
458
+ if (!this._isUndefined(args) && args !== null) {
459
+ params = this._getSearchParams(args, params);
460
+ }
461
+ this.batch.push({ indexName: indexName, params: params });
462
+ },
463
+ /*
464
+ * Clear all queries in cache
465
+ */
466
+ clearCache: function() {
467
+ this.cache = {};
468
+ },
469
+ /*
470
+ * Launch the batch of queries using XMLHttpRequest.
471
+ * (Optimized for browser using a POST query to minimize number of OPTIONS queries)
472
+ *
473
+ * @param callback the function that will receive results
474
+ * @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime.
475
+ */
476
+ sendQueriesBatch: function(callback, delay) {
477
+ var as = this;
478
+ var params = {requests: []};
479
+ for (var i = 0; i < as.batch.length; ++i) {
480
+ params.requests.push(as.batch[i]);
481
+ }
482
+ window.clearTimeout(as.onDelayTrigger);
483
+ if (!this._isUndefined(delay) && delay !== null && delay > 0) {
484
+ var onDelayTrigger = window.setTimeout( function() {
485
+ as._sendQueriesBatch(params, callback);
486
+ }, delay);
487
+ as.onDelayTrigger = onDelayTrigger;
488
+ } else {
489
+ return this._sendQueriesBatch(params, callback);
490
+ }
491
+ },
252
492
 
253
- },
254
- /**
255
- * Copy an existing index.
256
- * @param srcIndexName the name of index to copy.
257
- * @param dstIndexName the new index name that will contains a copy of srcIndexName (destination will be overriten if it already exist).
258
- * @param callback the result callback with two arguments
259
- * success: boolean set to true if the request was successfull
260
- * content: the server answer that contains the task ID
261
- */
262
- copyIndex: function(srcIndexName, dstIndexName, callback) {
263
- var postObj = {operation: 'copy', destination: dstIndexName};
264
- return this._jsonRequest({ method: 'POST',
265
- url: '/1/indexes/' + encodeURIComponent(srcIndexName) + '/operation',
266
- body: postObj,
267
- callback: callback });
268
- },
269
- /**
270
- * Return last log entries.
271
- * @param offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry).
272
- * @param length Specify the maximum number of entries to retrieve starting at offset. Maximum allowed value: 1000.
273
- * @param callback the result callback with two arguments
274
- * success: boolean set to true if the request was successfull
275
- * content: the server answer that contains the task ID
276
- */
277
- getLogs: function(callback, offset, length) {
278
- if (this._isUndefined(offset)) {
279
- offset = 0;
280
- }
281
- if (this._isUndefined(length)) {
282
- length = 10;
283
- }
493
+ /**
494
+ * Set the number of milliseconds a request can take before automatically being terminated.
495
+ *
496
+ * @param {Number} milliseconds
497
+ */
498
+ setRequestTimeout: function(milliseconds)
499
+ {
500
+ if (milliseconds) {
501
+ this.requestTimeoutInMs = parseInt(milliseconds, 10);
502
+ }
503
+ },
284
504
 
285
- return this._jsonRequest({ method: 'GET',
286
- url: '/1/logs?offset=' + offset + '&length=' + length,
287
- callback: callback });
288
- },
289
- /*
290
- * List all existing indexes (paginated)
291
- *
292
- * @param callback the result callback with two arguments
293
- * success: boolean set to true if the request was successfull
294
- * content: the server answer with index list or error description if success is false.
295
- * @param page The page to retrieve, starting at 0.
296
- */
297
- listIndexes: function(callback, page) {
298
- var params = typeof page !== 'undefined' ? '?page=' + page : '';
299
- return this._jsonRequest({ method: 'GET',
300
- url: '/1/indexes' + params,
301
- callback: callback });
302
- },
505
+ /*
506
+ * Index class constructor.
507
+ * You should not use this method directly but use initIndex() function
508
+ */
509
+ Index: function(algoliasearch, indexName) {
510
+ this.indexName = indexName;
511
+ this.as = algoliasearch;
512
+ this.typeAheadArgs = null;
513
+ this.typeAheadValueOption = null;
303
514
 
304
- /*
305
- * Get the index object initialized
306
- *
307
- * @param indexName the name of index
308
- * @param callback the result callback with one argument (the Index instance)
309
- */
310
- initIndex: function(indexName) {
311
- return new this.Index(this, indexName);
312
- },
313
- /*
314
- * List all existing user keys with their associated ACLs
315
- *
316
- * @param callback the result callback with two arguments
317
- * success: boolean set to true if the request was successfull
318
- * content: the server answer with user keys list or error description if success is false.
319
- */
320
- listUserKeys: function(callback) {
321
- return this._jsonRequest({ method: 'GET',
322
- url: '/1/keys',
323
- callback: callback });
324
- },
325
- /*
326
- * Get ACL of a user key
327
- *
328
- * @param callback the result callback with two arguments
329
- * success: boolean set to true if the request was successfull
330
- * content: the server answer with user keys list or error description if success is false.
331
- */
332
- getUserKeyACL: function(key, callback) {
333
- return this._jsonRequest({ method: 'GET',
334
- url: '/1/keys/' + key,
335
- callback: callback });
336
- },
337
- /*
338
- * Delete an existing user key
339
- *
340
- * @param callback the result callback with two arguments
341
- * success: boolean set to true if the request was successfull
342
- * content: the server answer with user keys list or error description if success is false.
343
- */
344
- deleteUserKey: function(key, callback) {
345
- return this._jsonRequest({ method: 'DELETE',
346
- url: '/1/keys/' + key,
347
- callback: callback });
348
- },
349
- /*
350
- * Add an existing user key
351
- *
352
- * @param acls the list of ACL for this key. Defined by an array of strings that
353
- * can contains the following values:
354
- * - search: allow to search (https and http)
355
- * - addObject: allows to add/update an object in the index (https only)
356
- * - deleteObject : allows to delete an existing object (https only)
357
- * - deleteIndex : allows to delete index content (https only)
358
- * - settings : allows to get index settings (https only)
359
- * - editSettings : allows to change index settings (https only)
360
- * @param callback the result callback with two arguments
361
- * success: boolean set to true if the request was successfull
362
- * content: the server answer with user keys list or error description if success is false.
363
- */
364
- addUserKey: function(acls, callback) {
365
- var aclsObject = {};
366
- aclsObject.acl = acls;
367
- return this._jsonRequest({ method: 'POST',
368
- url: '/1/keys',
369
- body: aclsObject,
370
- callback: callback });
371
- },
372
- /*
373
- * Add an existing user key
374
- *
375
- * @param acls the list of ACL for this key. Defined by an array of strings that
376
- * can contains the following values:
377
- * - search: allow to search (https and http)
378
- * - addObject: allows to add/update an object in the index (https only)
379
- * - deleteObject : allows to delete an existing object (https only)
380
- * - deleteIndex : allows to delete index content (https only)
381
- * - settings : allows to get index settings (https only)
382
- * - editSettings : allows to change index settings (https only)
383
- * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
384
- * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
385
- * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
386
- * @param callback the result callback with two arguments
387
- * success: boolean set to true if the request was successfull
388
- * content: the server answer with user keys list or error description if success is false.
389
- */
390
- addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
391
- var indexObj = this;
392
- var aclsObject = {};
393
- aclsObject.acl = acls;
394
- aclsObject.validity = validity;
395
- aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
396
- aclsObject.maxHitsPerQuery = maxHitsPerQuery;
397
- return this._jsonRequest({ method: 'POST',
398
- url: '/1/indexes/' + indexObj.indexName + '/keys',
399
- body: aclsObject,
400
- callback: callback });
401
- },
515
+ // make sure every index instance has it's own cache
516
+ this.cache = {};
517
+ },
518
+ /**
519
+ * Add an extra field to the HTTP request
520
+ *
521
+ * @param key the header field name
522
+ * @param value the header field value
523
+ */
524
+ setExtraHeader: function(key, value) {
525
+ this.extraHeaders.push({ key: key, value: value});
526
+ },
402
527
 
403
- /**
404
- * Set the extra security tagFilters header
405
- * @param {string|array} tags The list of tags defining the current security filters
406
- */
407
- setSecurityTags: function(tags) {
408
- if (Object.prototype.toString.call(tags) === '[object Array]') {
409
- var strTags = [];
410
- for (var i = 0; i < tags.length; ++i) {
411
- if (Object.prototype.toString.call(tags[i]) === '[object Array]') {
412
- var oredTags = [];
413
- for (var j = 0; j < tags[i].length; ++j) {
414
- oredTags.push(tags[i][j]);
415
- }
416
- strTags.push('(' + oredTags.join(',') + ')');
417
- } else {
418
- strTags.push(tags[i]);
419
- }
420
- }
421
- tags = strTags.join(',');
528
+ _sendQueriesBatch: function(params, callback) {
529
+ if (this.jsonp === null) {
530
+ var self = this;
531
+ return this._jsonRequest({ cache: this.cache,
532
+ method: 'POST',
533
+ url: '/1/indexes/*/queries',
534
+ body: params,
535
+ callback: function(success, content) {
536
+ if (!success) {
537
+ // retry first with JSONP
538
+ self.jsonp = true;
539
+ self._sendQueriesBatch(params, callback);
540
+ } else {
541
+ self.jsonp = false;
542
+ callback && callback(success, content);
543
+ }
422
544
  }
423
- this.tagFilters = tags;
424
- },
425
-
426
- /**
427
- * Set the extra user token header
428
- * @param {string} userToken The token identifying a uniq user (used to apply rate limits)
429
- */
430
- setUserToken: function(userToken) {
431
- this.userToken = userToken;
432
- },
545
+ });
546
+ } else if (this.jsonp) {
547
+ var jsonpParams = '';
548
+ for (var i = 0; i < params.requests.length; ++i) {
549
+ var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params;
550
+ jsonpParams += i + '=' + encodeURIComponent(q) + '&';
551
+ }
552
+ var pObj = {params: jsonpParams};
553
+ return this._jsonRequest({ cache: this.cache,
554
+ method: 'GET',
555
+ url: '/1/indexes/*',
556
+ body: pObj,
557
+ callback: callback });
558
+ } else {
559
+ return this._jsonRequest({ cache: this.cache,
560
+ method: 'POST',
561
+ url: '/1/indexes/*/queries',
562
+ body: params,
563
+ callback: callback});
564
+ }
565
+ },
566
+ /*
567
+ * Wrapper that try all hosts to maximize the quality of service
568
+ */
569
+ _jsonRequest: function(opts) {
570
+ var self = this;
571
+ var callback = opts.callback;
572
+ var cache = null;
573
+ var cacheID = opts.url;
574
+ var deferred = null;
575
+ if (this.options.jQuery) {
576
+ deferred = this.options.jQuery.$.Deferred();
577
+ deferred.promise = deferred.promise(); // promise is a property in angular
578
+ } else if (this.options.angular) {
579
+ deferred = this.options.angular.$q.defer();
580
+ }
433
581
 
434
- /*
435
- * Initialize a new batch of search queries
436
- */
437
- startQueriesBatch: function() {
438
- this.batch = [];
439
- },
440
- /*
441
- * Add a search query in the batch
442
- *
443
- * @param query the full text query
444
- * @param args (optional) if set, contains an object with query parameters:
445
- * - attributes: an array of object attribute names to retrieve
446
- * (if not set all attributes are retrieve)
447
- * - attributesToHighlight: an array of object attribute names to highlight
448
- * (if not set indexed attributes are highlighted)
449
- * - minWordSizefor1Typo: the minimum number of characters to accept one typo.
450
- * Defaults to 3.
451
- * - minWordSizefor2Typos: the minimum number of characters to accept two typos.
452
- * Defaults to 7.
453
- * - getRankingInfo: if set, the result hits will contain ranking information in
454
- * _rankingInfo attribute
455
- * - page: (pagination parameter) page to retrieve (zero base). Defaults to 0.
456
- * - hitsPerPage: (pagination parameter) number of hits per page. Defaults to 10.
457
- */
458
- addQueryInBatch: function(indexName, query, args) {
459
- var params = 'query=' + encodeURIComponent(query);
460
- if (!this._isUndefined(args) && args !== null) {
461
- params = this._getSearchParams(args, params);
582
+ if (!this._isUndefined(opts.body)) {
583
+ cacheID = opts.url + '_body_' + JSON.stringify(opts.body);
584
+ }
585
+ if (!this._isUndefined(opts.cache)) {
586
+ cache = opts.cache;
587
+ if (!this._isUndefined(cache[cacheID])) {
588
+ if (!this._isUndefined(callback) && callback) {
589
+ setTimeout(function () { callback(true, cache[cacheID]); }, 1);
462
590
  }
463
- this.batch.push({ indexName: indexName, params: params });
464
- },
465
- /*
466
- * Clear all queries in cache
467
- */
468
- clearCache: function() {
469
- this.cache = {};
470
- },
471
- /*
472
- * Launch the batch of queries using XMLHttpRequest.
473
- * (Optimized for browser using a POST query to minimize number of OPTIONS queries)
474
- *
475
- * @param callback the function that will receive results
476
- * @param delay (optional) if set, wait for this delay (in ms) and only send the batch if there was no other in the meantime.
477
- */
478
- sendQueriesBatch: function(callback, delay) {
479
- var as = this;
480
- var params = {requests: []};
481
- for (var i = 0; i < as.batch.length; ++i) {
482
- params.requests.push(as.batch[i]);
591
+ deferred && deferred.resolve(cache[cacheID]);
592
+ return deferred && deferred.promise;
593
+ }
594
+ }
595
+
596
+ opts.successiveRetryCount = 0;
597
+ var impl = function() {
598
+ if (opts.successiveRetryCount >= self.hosts.length) {
599
+ var error = { message: 'Cannot connect the Algolia\'s Search API. Please send an email to support@algolia.com to report the issue.' };
600
+ if (!self._isUndefined(callback) && callback) {
601
+ opts.successiveRetryCount = 0;
602
+ callback(false, error);
483
603
  }
484
- window.clearTimeout(as.onDelayTrigger);
485
- if (!this._isUndefined(delay) && delay !== null && delay > 0) {
486
- var onDelayTrigger = window.setTimeout( function() {
487
- as._sendQueriesBatch(params, callback);
488
- }, delay);
489
- as.onDelayTrigger = onDelayTrigger;
604
+ deferred && deferred.reject(error);
605
+ return;
606
+ }
607
+ opts.callback = function(retry, success, body) {
608
+ if (success && !self._isUndefined(opts.cache)) {
609
+ cache[cacheID] = body;
610
+ }
611
+ if (!success && retry) {
612
+ self.currentHostIndex = ++self.currentHostIndex % self.hosts.length;
613
+ opts.successiveRetryCount += 1;
614
+ impl();
490
615
  } else {
491
- return this._sendQueriesBatch(params, callback);
616
+ opts.successiveRetryCount = 0;
617
+ deferred && (success ? deferred.resolve(body) : deferred.reject(body));
618
+ if (!self._isUndefined(callback) && callback) {
619
+ callback(success, body);
620
+ }
492
621
  }
493
- },
622
+ };
623
+ opts.hostname = self.hosts[self.currentHostIndex];
624
+ self._jsonRequestByHost(opts);
625
+ };
626
+ impl();
494
627
 
495
- /**
496
- * Set the number of milliseconds a request can take before automatically being terminated.
497
- *
498
- * @param {Number} milliseconds
499
- */
500
- setRequestTimeout: function(milliseconds)
501
- {
502
- if (milliseconds) {
503
- this.requestTimeoutInMs = parseInt(milliseconds, 10);
504
- }
505
- },
628
+ return deferred && deferred.promise;
629
+ },
506
630
 
507
- /*
508
- * Index class constructor.
509
- * You should not use this method directly but use initIndex() function
510
- */
511
- Index: function(algoliasearch, indexName) {
512
- this.indexName = indexName;
513
- this.as = algoliasearch;
514
- this.typeAheadArgs = null;
515
- this.typeAheadValueOption = null;
516
-
517
- // make sure every index instance has it's own cache
518
- this.cache = {};
519
- },
520
- /**
521
- * Add an extra field to the HTTP request
522
- *
523
- * @param key the header field name
524
- * @param value the header field value
525
- */
526
- setExtraHeader: function(key, value) {
527
- this.extraHeaders.push({ key: key, value: value});
528
- },
631
+ _jsonRequestByHost: function(opts) {
632
+ var self = this;
633
+ var url = opts.hostname + opts.url;
634
+
635
+ if (this.jsonp) {
636
+ this._makeJsonpRequestByHost(url, opts);
637
+ } else if (this.options.jQuery) {
638
+ this._makejQueryRequestByHost(url, opts);
639
+ } else if (this.options.angular) {
640
+ this._makeAngularRequestByHost(url, opts);
641
+ } else {
642
+ this._makeXmlHttpRequestByHost(url, opts);
643
+ }
644
+ },
529
645
 
530
- _sendQueriesBatch: function(params, callback) {
531
-
532
- if (this.jsonp === null) {
533
- var self = this;
534
- return this._jsonRequest({ cache: this.cache,
535
- method: 'POST',
536
- url: '/1/indexes/*/queries',
537
- body: params,
538
- callback: function(success, content) {
539
- if (!success) {
540
- // retry first with JSONP
541
- self.jsonp = true;
542
- self._sendQueriesBatch(params, callback);
543
- } else {
544
- self.jsonp = false;
545
- callback && callback(success, content);
546
- }
547
- }
548
- });
549
- } else if (this.jsonp) {
550
- var jsonpParams = '';
551
- for (var i = 0; i < params.requests.length; ++i) {
552
- var q = '/1/indexes/' + encodeURIComponent(params.requests[i].indexName) + '?' + params.requests[i].params;
553
- jsonpParams += i + '=' + encodeURIComponent(q) + '&';
554
- }
555
- var pObj = {params: jsonpParams};
556
- return this._jsonRequest({ cache: this.cache,
557
- method: 'GET',
558
- url: '/1/indexes/*',
559
- body: pObj,
560
- callback: callback });
561
- } else {
562
- return this._jsonRequest({ cache: this.cache,
563
- method: 'POST',
564
- url: '/1/indexes/*/queries',
565
- body: params,
566
- callback: callback});
567
- }
568
- },
569
- /*
570
- * Wrapper that try all hosts to maximize the quality of service
571
- */
572
- _jsonRequest: function(opts) {
573
- var self = this;
574
- var callback = opts.callback;
575
- var cache = null;
576
- var cacheID = opts.url;
577
- var deferred = null;
578
- if (this.options.jQuery) {
579
- deferred = this.options.jQuery.$.Deferred();
580
- deferred.promise = deferred.promise(); // promise is a property in angular
581
- } else if (this.options.angular) {
582
- deferred = this.options.angular.$q.defer();
583
- }
646
+ /**
647
+ * Make a $http
648
+ *
649
+ * @param url request url (includes endpoint and path)
650
+ * @param opts all request opts
651
+ */
652
+ _makeAngularRequestByHost: function(url, opts) {
653
+ var self = this;
654
+ var body = null;
584
655
 
585
- if (!this._isUndefined(opts.body)) {
586
- cacheID = opts.url + '_body_' + JSON.stringify(opts.body);
587
- }
588
- if (!this._isUndefined(opts.cache)) {
589
- cache = opts.cache;
590
- if (!this._isUndefined(cache[cacheID])) {
591
- if (!this._isUndefined(callback) && callback) {
592
- setTimeout(function () { callback(true, cache[cacheID]); }, 1);
593
- }
594
- deferred && deferred.resolve(cache[cacheID]);
595
- return deferred && deferred.promise;
596
- }
597
- }
656
+ if (!this._isUndefined(opts.body)) {
657
+ body = JSON.stringify(opts.body);
658
+ }
598
659
 
599
- opts.successiveRetryCount = 0;
600
- var impl = function() {
601
- if (opts.successiveRetryCount >= self.hosts.length) {
602
- var error = { message: 'Cannot connect the Algolia\'s Search API. Please send an email to support@algolia.com to report the issue.' };
603
- if (!self._isUndefined(callback) && callback) {
604
- opts.successiveRetryCount = 0;
605
- callback(false, error);
606
- }
607
- deferred && deferred.reject(error);
608
- return;
609
- }
610
- opts.callback = function(retry, success, obj, body) {
611
- if (success && !self._isUndefined(opts.cache)) {
612
- cache[cacheID] = body;
613
- }
614
- if (!success && retry) {
615
- self.currentHostIndex = ++self.currentHostIndex % self.hosts.length;
616
- opts.successiveRetryCount += 1;
617
- impl();
618
- } else {
619
- opts.successiveRetryCount = 0;
620
- deferred && (success ? deferred.resolve(body) : deferred.reject(body));
621
- if (!self._isUndefined(callback) && callback) {
622
- callback(success, body);
623
- }
624
- }
625
- };
626
- opts.hostname = self.hosts[self.currentHostIndex];
627
- self._jsonRequestByHost(opts);
628
- };
629
- impl();
660
+ url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
661
+ url += '&X-Algolia-Application-Id=' + this.applicationID;
662
+ if (this.userToken) {
663
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
664
+ }
665
+ if (this.tagFilters) {
666
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
667
+ }
668
+ for (var i = 0; i < this.extraHeaders.length; ++i) {
669
+ url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
670
+ }
671
+ this.options.angular.$http({
672
+ url: url,
673
+ method: opts.method,
674
+ data: body,
675
+ cache: false,
676
+ timeout: (this.requestTimeoutInMs * (opts.successiveRetryCount + 1))
677
+ }).then(function(response) {
678
+ opts.callback(false, true, response.data);
679
+ }, function(response) {
680
+ if (response.status === 0) {
681
+ // xhr.timeout is not handled by Angular.js right now
682
+ // let's retry
683
+ opts.callback(true, false, response.data);
684
+ } else if (response.status == 400 || response.status === 403 || response.status === 404) {
685
+ opts.callback(false, false, response.data);
686
+ } else {
687
+ opts.callback(true, false, response.data);
688
+ }
689
+ });
690
+ },
630
691
 
631
- return deferred && deferred.promise;
632
- },
692
+ /**
693
+ * Make a $.ajax
694
+ *
695
+ * @param url request url (includes endpoint and path)
696
+ * @param opts all request opts
697
+ */
698
+ _makejQueryRequestByHost: function(url, opts) {
699
+ var self = this;
700
+ var body = null;
633
701
 
634
- _jsonRequestByHost: function(opts) {
635
- var self = this;
636
- var url = opts.hostname + opts.url;
702
+ if (!this._isUndefined(opts.body)) {
703
+ body = JSON.stringify(opts.body);
704
+ }
637
705
 
638
- if (this.jsonp) {
639
- this._makeJsonpRequestByHost(url, opts);
640
- } else if (this.options.jQuery) {
641
- this._makejQueryRequestByHost(url, opts);
642
- } else if (this.options.angular) {
643
- this._makeAngularRequestByHost(url, opts);
706
+ url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
707
+ url += '&X-Algolia-Application-Id=' + this.applicationID;
708
+ if (this.userToken) {
709
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
710
+ }
711
+ if (this.tagFilters) {
712
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
713
+ }
714
+ for (var i = 0; i < this.extraHeaders.length; ++i) {
715
+ url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
716
+ }
717
+ this.options.jQuery.$.ajax(url, {
718
+ type: opts.method,
719
+ timeout: (this.requestTimeoutInMs * (opts.successiveRetryCount + 1)),
720
+ dataType: 'json',
721
+ data: body,
722
+ error: function(xhr, textStatus, error) {
723
+ if (textStatus === 'timeout') {
724
+ opts.callback(true, false, { 'message': 'Timeout - Could not connect to endpoint ' + url } );
725
+ } else if (xhr.status === 400 || xhr.status === 403 || xhr.status === 404) {
726
+ opts.callback(false, false, xhr.responseJSON );
644
727
  } else {
645
- this._makeXmlHttpRequestByHost(url, opts);
728
+ opts.callback(true, false, { 'message': error } );
646
729
  }
647
- },
730
+ },
731
+ success: function(data, textStatus, xhr) {
732
+ opts.callback(false, true, data);
733
+ }
734
+ });
735
+ },
648
736
 
649
- /**
650
- * Make a $http
651
- *
652
- * @param url request url (includes endpoint and path)
653
- * @param opts all request opts
654
- */
655
- _makeAngularRequestByHost: function(url, opts) {
656
- var self = this;
657
- var body = null;
737
+ /**
738
+ * Make a JSONP request
739
+ *
740
+ * @param url request url (includes endpoint and path)
741
+ * @param opts all request options
742
+ */
743
+ _makeJsonpRequestByHost: function(url, opts) {
744
+ if (opts.method !== 'GET') {
745
+ opts.callback(true, false, { 'message': 'Method ' + opts.method + ' ' + url + ' is not supported by JSONP.' });
746
+ return;
747
+ }
658
748
 
659
- if (!this._isUndefined(opts.body)) {
660
- body = JSON.stringify(opts.body);
661
- }
749
+ var cbCalled = false;
750
+ var timedOut = false;
662
751
 
663
- url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
664
- url += '&X-Algolia-Application-Id=' + this.applicationID;
665
- if (this.userToken) {
666
- url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
667
- }
668
- if (this.tagFilters) {
669
- url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
670
- }
671
- for (var i = 0; i < this.extraHeaders.length; ++i) {
672
- url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
673
- }
674
- this.options.angular.$http({
675
- url: url,
676
- method: opts.method,
677
- data: body,
678
- cache: false,
679
- timeout: (this.requestTimeoutInMs * (opts.successiveRetryCount + 1))
680
- }).then(function(response) {
681
- opts.callback(false, true, null, response.data);
682
- }, function(response) {
683
- if (response.status === 0) {
684
- // xhr.timeout is not handled by Angular.js right now
685
- // let's retry
686
- opts.callback(true, false, null, response.data);
687
- } else if (response.status == 400 || response.status === 403 || response.status === 404) {
688
- opts.callback(false, false, null, response.data);
689
- } else {
690
- opts.callback(true, false, null, response.data);
691
- }
692
- });
693
- },
752
+ AlgoliaSearch.JSONPCounter += 1;
753
+ var head = document.getElementsByTagName('head')[0];
754
+ var script = document.createElement('script');
755
+ var cb = 'algoliaJSONP_' + AlgoliaSearch.JSONPCounter;
756
+ var done = false;
757
+ var ontimeout;
758
+ var success;
759
+ var clean;
694
760
 
695
- /**
696
- * Make a $.ajax
697
- *
698
- * @param url request url (includes endpoint and path)
699
- * @param opts all request opts
700
- */
701
- _makejQueryRequestByHost: function(url, opts) {
702
- var self = this;
703
- var body = null;
761
+ window[cb] = function(data) {
762
+ try { delete window[cb]; } catch (e) { window[cb] = undefined; }
704
763
 
705
- if (!this._isUndefined(opts.body)) {
706
- body = JSON.stringify(opts.body);
707
- }
764
+ if (timedOut) {
765
+ return;
766
+ }
708
767
 
709
- url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
710
- url += '&X-Algolia-Application-Id=' + this.applicationID;
711
- if (this.userToken) {
712
- url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
713
- }
714
- if (this.tagFilters) {
715
- url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
716
- }
717
- for (var i = 0; i < this.extraHeaders.length; ++i) {
718
- url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
719
- }
720
- this.options.jQuery.$.ajax(url, {
721
- type: opts.method,
722
- timeout: (this.requestTimeoutInMs * (opts.successiveRetryCount + 1)),
723
- dataType: 'json',
724
- data: body,
725
- error: function(xhr, textStatus, error) {
726
- if (textStatus === 'timeout') {
727
- opts.callback(true, false, null, { 'message': 'Timeout - Could not connect to endpoint ' + url } );
728
- } else if (xhr.status === 400 || xhr.status === 403 || xhr.status === 404) {
729
- opts.callback(false, false, null, xhr.responseJSON );
730
- } else {
731
- opts.callback(true, false, null, { 'message': error } );
732
- }
733
- },
734
- success: function(data, textStatus, xhr) {
735
- opts.callback(false, true, null, data);
736
- }
737
- });
738
- },
768
+ var status =
769
+ data && data.message && data.status ||
770
+ data && 200;
739
771
 
740
- /**
741
- * Make a JSONP request
742
- *
743
- * @param url request url (includes endpoint and path)
744
- * @param opts all request options
745
- */
746
- _makeJsonpRequestByHost: function(url, opts) {
747
- if (opts.method !== 'GET') {
748
- opts.callback(true, false, null, { 'message': 'Method ' + opts.method + ' ' + url + ' is not supported by JSONP.' });
749
- return;
750
- }
772
+ var ok = status === 200;
773
+ var retry = !ok && status !== 400 && status !== 403 && status !== 404;
774
+ cbCalled = true;
775
+ opts.callback(retry, ok, data);
776
+ };
751
777
 
752
- this.jsonpCounter = this.jsonpCounter || 0;
753
- this.jsonpCounter += 1;
754
- var head = document.getElementsByTagName('head')[0];
755
- var script = document.createElement('script');
756
- var cb = 'algoliaJSONP_' + this.jsonpCounter;
757
- var done = false;
758
- var ontimeout = null;
759
-
760
- window[cb] = function(data) {
761
- opts.callback(false, true, null, data);
762
- try { delete window[cb]; } catch (e) { window[cb] = undefined; }
763
- };
778
+ script.type = 'text/javascript';
779
+ url += '?callback=' + cb + '&X-Algolia-Application-Id=' + this.applicationID + '&X-Algolia-API-Key=' + this.apiKey;
764
780
 
765
- script.type = 'text/javascript';
766
- script.src = url + '?callback=' + cb + '&X-Algolia-Application-Id=' + this.applicationID + '&X-Algolia-API-Key=' + this.apiKey;
781
+ if (this.tagFilters) {
782
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
783
+ }
767
784
 
768
- if (this.tagFilters) {
769
- script.src += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
770
- }
785
+ if (this.userToken) {
786
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
787
+ }
771
788
 
772
- if (this.userToken) {
773
- script.src += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
774
- }
775
- for (var i = 0; i < this.extraHeaders.length; ++i) {
776
- script.src += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
777
- }
789
+ for (var i = 0; i < this.extraHeaders.length; ++i) {
790
+ url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
791
+ }
778
792
 
793
+ if (opts.body && opts.body.params) {
794
+ url += '&' + opts.body.params;
795
+ }
779
796
 
780
- if (opts.body && opts.body.params) {
781
- script.src += '&' + opts.body.params;
782
- }
797
+ ontimeout = setTimeout(function() {
798
+ timedOut = true;
799
+ clean();
783
800
 
784
- ontimeout = setTimeout(function() {
785
- script.onload = script.onreadystatechange = script.onerror = null;
786
- window[cb] = function(data) {
787
- try { delete window[cb]; } catch (e) { window[cb] = undefined; }
788
- };
801
+ opts.callback(true, false, { 'message': 'Timeout - Failed to load JSONP script.' });
802
+ }, this.requestTimeoutInMs);
789
803
 
790
- opts.callback(true, false, null, { 'message': 'Timeout - Failed to load JSONP script.' });
791
- head.removeChild(script);
804
+ success = function() {
805
+ if (done || timedOut) {
806
+ return;
807
+ }
792
808
 
793
- clearTimeout(ontimeout);
794
- ontimeout = null;
809
+ done = true;
810
+ clean();
795
811
 
796
- }, this.requestTimeoutInMs);
812
+ // script loaded but did not call the fn => script loading error
813
+ if (!cbCalled) {
814
+ opts.callback(true, false, { 'message': 'Failed to load JSONP script.' });
815
+ }
816
+ };
797
817
 
798
- script.onload = script.onreadystatechange = function() {
799
- clearTimeout(ontimeout);
800
- ontimeout = null;
818
+ clean = function() {
819
+ clearTimeout(ontimeout);
820
+ script.onload = null;
821
+ script.onreadystatechange = null;
822
+ script.onerror = null;
823
+ head.removeChild(script);
801
824
 
802
- if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
803
- done = true;
825
+ try {
826
+ delete window[cb];
827
+ delete window[cb + '_loaded'];
828
+ } catch (e) {
829
+ window[cb] = null;
830
+ window[cb + '_loaded'] = null;
831
+ }
832
+ };
804
833
 
805
- if (typeof window[cb + '_loaded'] === 'undefined') {
806
- opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' });
807
- try { delete window[cb]; } catch (e) { window[cb] = undefined; }
808
- } else {
809
- try { delete window[cb + '_loaded']; } catch (e) { window[cb + '_loaded'] = undefined; }
810
- }
811
- script.onload = script.onreadystatechange = null; // Handle memory leak in IE
812
- head.removeChild(script);
813
- }
814
- };
834
+ // script onreadystatechange needed only for
835
+ // <= IE8
836
+ // https://github.com/angular/angular.js/issues/4523
837
+ script.onreadystatechange = function() {
838
+ if (this.readyState === 'loaded' || this.readyState === 'complete') {
839
+ success();
840
+ }
841
+ };
815
842
 
816
- script.onerror = function() {
817
- clearTimeout(ontimeout);
818
- ontimeout = null;
843
+ script.onload = function() {
844
+ success();
845
+ };
819
846
 
820
- opts.callback(true, false, null, { 'message': 'Failed to load JSONP script.' });
821
- head.removeChild(script);
822
- try { delete window[cb]; } catch (e) { window[cb] = undefined; }
823
- };
847
+ script.onerror = function() {
848
+ if (done || timedOut) {
849
+ return;
850
+ }
824
851
 
825
- head.appendChild(script);
826
- },
852
+ clean();
853
+ opts.callback(true, false, { 'message': 'Failed to load JSONP script.' });
854
+ };
827
855
 
828
- /**
829
- * Make a XmlHttpRequest
830
- *
831
- * @param url request url (includes endpoint and path)
832
- * @param opts all request opts
833
- */
834
- _makeXmlHttpRequestByHost: function(url, opts) {
835
- var self = this;
836
- var xmlHttp = window.XMLHttpRequest ? new XMLHttpRequest() : {};
837
- var body = null;
838
- var ontimeout = null;
839
-
840
- if (!this._isUndefined(opts.body)) {
841
- body = JSON.stringify(opts.body);
842
- }
856
+ script.async = true;
857
+ script.defer = true;
858
+ script.src = url;
843
859
 
844
- url += ((url.indexOf('?') === -1) ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
845
- url += '&X-Algolia-Application-Id=' + this.applicationID;
846
- if (this.userToken) {
847
- url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
848
- }
849
- if (this.tagFilters) {
850
- url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
851
- }
852
- for (var i = 0; i < this.extraHeaders.length; ++i) {
853
- url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
854
- }
855
- if ('withCredentials' in xmlHttp) {
856
- xmlHttp.open(opts.method, url, true);
857
- xmlHttp.timeout = this.requestTimeoutInMs * (opts.successiveRetryCount + 1);
858
- if (body !== null) {
859
- /* This content type is specified to follow CORS 'simple header' directive */
860
- xmlHttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
861
- }
862
- } else if (typeof XDomainRequest !== 'undefined') {
863
- // Handle IE8/IE9
864
- // XDomainRequest only exists in IE, and is IE's way of making CORS requests.
865
- xmlHttp = new XDomainRequest();
866
- xmlHttp.open(opts.method, url);
867
- } else {
868
- // very old browser, not supported
869
- opts.callback(false, false, null, { 'message': 'CORS not supported' });
870
- return;
871
- }
860
+ head.appendChild(script);
861
+ },
872
862
 
873
- ontimeout = setTimeout(function() {
874
- xmlHttp.abort();
875
- // Prevent Internet Explorer 9, JScript Error c00c023f
876
- if (xmlHttp.aborted === true) {
877
- stopLoadAnimation();
878
- return;
879
- }
880
- opts.callback(true, false, null, { 'message': 'Timeout - Could not connect to endpoint ' + url } );
863
+ /**
864
+ * Make a XmlHttpRequest
865
+ *
866
+ * @param url request url (includes endpoint and path)
867
+ * @param opts all request opts
868
+ */
869
+ _makeXmlHttpRequestByHost: function(url, opts) {
870
+ // no cors or XDomainRequest, no request
871
+ if (!this._support.cors && !this._support.hasXDomainRequest) {
872
+ // very old browser, not supported
873
+ opts.callback(false, false, { 'message': 'CORS not supported' });
874
+ return;
875
+ }
881
876
 
882
- clearTimeout(ontimeout);
883
- ontimeout = null;
877
+ var body = null;
878
+ var request = this._support.cors ? new XMLHttpRequest() : new XDomainRequest();
879
+ var ontimeout;
880
+ var self = this;
881
+ var timedOut;
882
+ var timeoutListener;
884
883
 
885
- }, this.requestTimeoutInMs * (opts.successiveRetryCount + 1));
884
+ if (!this._isUndefined(opts.body)) {
885
+ body = JSON.stringify(opts.body);
886
+ }
886
887
 
887
- xmlHttp.onload = function(event) {
888
- clearTimeout(ontimeout);
889
- ontimeout = null;
888
+ url += (url.indexOf('?') === -1 ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
889
+ url += '&X-Algolia-Application-Id=' + this.applicationID;
890
890
 
891
- if (!self._isUndefined(event) && event.target !== null) {
892
- var success = false;
893
- var response = null;
891
+ if (this.userToken) {
892
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
893
+ }
894
894
 
895
- if (typeof XDomainRequest !== 'undefined') {
896
- // Handle CORS requests IE8/IE9
897
- response = event.target.responseText;
898
- success = (response && response.length > 0);
899
- } else {
900
- response = event.target.response;
901
- success = (event.target.status === 200 || event.target.status === 201);
902
- }
895
+ if (this.tagFilters) {
896
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
897
+ }
903
898
 
904
- var retry = !success && event.target.status !== 400 && event.target.status !== 403 && event.target.status !== 404;
905
- opts.callback(retry, success, event.target, response ? JSON.parse(response) : null);
906
- } else {
907
- opts.callback(false, true, event, JSON.parse(xmlHttp.responseText));
908
- }
909
- };
910
- xmlHttp.ontimeout = function(event) { // stop the network call but rely on ontimeout to call opt.callback
911
- };
912
- xmlHttp.onerror = function(event) {
913
- clearTimeout(ontimeout);
914
- ontimeout = null;
915
- opts.callback(true, false, null, { 'message': 'Could not connect to host', 'error': event } );
916
- };
899
+ for (var i = 0; i < this.extraHeaders.length; ++i) {
900
+ url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
901
+ }
917
902
 
918
- xmlHttp.send(body);
919
- },
903
+ timeoutListener = function() {
904
+ if (!self._support.timeout) {
905
+ timedOut = true;
906
+ request.abort();
907
+ }
920
908
 
921
- /*
922
- * Transform search param object in query string
923
- */
924
- _getSearchParams: function(args, params) {
925
- if (this._isUndefined(args) || args === null) {
926
- return params;
927
- }
928
- for (var key in args) {
929
- if (key !== null && args.hasOwnProperty(key)) {
930
- params += (params.length === 0) ? '?' : '&';
931
- params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]);
932
- }
933
- }
934
- return params;
935
- },
936
- _isUndefined: function(obj) {
937
- return obj === void 0;
938
- },
909
+ opts.callback(true, false, { 'message': 'Timeout - Could not connect to endpoint ' + url } );
910
+ };
911
+
912
+ // do not rely on default XHR async flag, as some analytics code like hotjar
913
+ // breaks it and set it to false by default
914
+ if (request instanceof XMLHttpRequest) {
915
+ request.open(opts.method, url, true);
916
+ } else {
917
+ request.open(opts.method, url);
918
+ }
919
+
920
+ if (this._support.cors && body !== null && opts.method !== 'GET') {
921
+ request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
922
+ }
923
+
924
+ // event object not received in IE8, at least
925
+ // but we do not use it, still important to note
926
+ request.onload = function(/*event*/) {
927
+ // When browser does not supports request.timeout, we can
928
+ // have both a load and timeout event
929
+ if (timedOut) {
930
+ return;
931
+ }
932
+
933
+ if (!self._support.timeout) {
934
+ clearTimeout(ontimeout);
935
+ }
936
+
937
+ var response = null;
938
+
939
+ try {
940
+ response = JSON.parse(request.responseText);
941
+ } catch(e) {}
939
942
 
940
- /// internal attributes
941
- applicationID: null,
942
- apiKey: null,
943
- tagFilters: null,
944
- userToken: null,
945
- hosts: [],
946
- extraHeaders: []
943
+ var status =
944
+ // XHR provides a `status` property
945
+ request.status ||
946
+
947
+ // XDR does not have a `status` property,
948
+ // we rely on our own API response `status`, only
949
+ // provided when an error occurs, so we expect a .message
950
+ response && response.message && response.status ||
951
+
952
+ // XDR default to success when no response.status
953
+ response && 200;
954
+
955
+ var success = status === 200 || status === 201;
956
+ var retry = !success && status !== 400 && status !== 403 && status !== 404;
957
+
958
+ opts.callback(retry, success, response);
959
+ };
960
+
961
+ if (this._support.timeout) {
962
+ // .timeout supported by both XHR and XDR,
963
+ // we do receive timeout event, tested
964
+ request.timeout = this.requestTimeoutInMs * (opts.successiveRetryCount + 1);
965
+
966
+ request.ontimeout = timeoutListener;
967
+ } else {
968
+ ontimeout = setTimeout(timeoutListener, this.requestTimeoutInMs * (opts.successiveRetryCount + 1));
969
+ }
970
+
971
+ request.onerror = function(event) {
972
+ if (timedOut) {
973
+ return;
974
+ }
975
+
976
+ if (!self._support.timeout) {
977
+ clearTimeout(ontimeout);
978
+ }
979
+
980
+ // error event is trigerred both with XDR/XHR on:
981
+ // - DNS error
982
+ // - unallowed cross domain request
983
+ opts.callback(true, false, { 'message': 'Could not connect to host', 'error': event } );
984
+ };
985
+
986
+ request.send(body);
987
+ },
988
+
989
+ /*
990
+ * Transform search param object in query string
991
+ */
992
+ _getSearchParams: function(args, params) {
993
+ if (this._isUndefined(args) || args === null) {
994
+ return params;
995
+ }
996
+ for (var key in args) {
997
+ if (key !== null && args.hasOwnProperty(key)) {
998
+ params += (params.length === 0) ? '?' : '&';
999
+ params += key + '=' + encodeURIComponent(Object.prototype.toString.call(args[key]) === '[object Array]' ? JSON.stringify(args[key]) : args[key]);
1000
+ }
1001
+ }
1002
+ return params;
1003
+ },
1004
+ _isUndefined: function(obj) {
1005
+ return obj === void 0;
1006
+ },
1007
+
1008
+ _support: {
1009
+ hasXMLHttpRequest: 'XMLHttpRequest' in window,
1010
+ hasXDomainRequest: 'XDomainRequest' in window,
1011
+ cors: 'withCredentials' in new XMLHttpRequest(),
1012
+ timeout: 'timeout' in new XMLHttpRequest()
1013
+ }
947
1014
  };
948
1015
 
949
1016
  /*
@@ -951,575 +1018,571 @@ AlgoliaSearch.prototype = {
951
1018
  * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object
952
1019
  */
953
1020
  AlgoliaSearch.prototype.Index.prototype = {
954
- /*
955
- * Clear all queries in cache
956
- */
957
- clearCache: function() {
958
- this.cache = {};
959
- },
960
- /*
961
- * Add an object in this index
962
- *
963
- * @param content contains the javascript object to add inside the index
964
- * @param callback (optional) the result callback with two arguments:
965
- * success: boolean set to true if the request was successfull
966
- * content: the server answer that contains 3 elements: createAt, taskId and objectID
967
- * @param objectID (optional) an objectID you want to attribute to this object
968
- * (if the attribute already exist the old object will be overwrite)
969
- */
970
- addObject: function(content, callback, objectID) {
971
- var indexObj = this;
972
- if (this.as._isUndefined(objectID)) {
973
- return this.as._jsonRequest({ method: 'POST',
974
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName),
975
- body: content,
976
- callback: callback });
977
- } else {
978
- return this.as._jsonRequest({ method: 'PUT',
979
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
980
- body: content,
981
- callback: callback });
982
- }
1021
+ /*
1022
+ * Clear all queries in cache
1023
+ */
1024
+ clearCache: function() {
1025
+ this.cache = {};
1026
+ },
1027
+ /*
1028
+ * Add an object in this index
1029
+ *
1030
+ * @param content contains the javascript object to add inside the index
1031
+ * @param callback (optional) the result callback with two arguments:
1032
+ * success: boolean set to true if the request was successfull
1033
+ * content: the server answer that contains 3 elements: createAt, taskId and objectID
1034
+ * @param objectID (optional) an objectID you want to attribute to this object
1035
+ * (if the attribute already exist the old object will be overwrite)
1036
+ */
1037
+ addObject: function(content, callback, objectID) {
1038
+ var indexObj = this;
1039
+ if (this.as._isUndefined(objectID)) {
1040
+ return this.as._jsonRequest({ method: 'POST',
1041
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName),
1042
+ body: content,
1043
+ callback: callback });
1044
+ } else {
1045
+ return this.as._jsonRequest({ method: 'PUT',
1046
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
1047
+ body: content,
1048
+ callback: callback });
1049
+ }
983
1050
 
984
- },
985
- /*
986
- * Add several objects
987
- *
988
- * @param objects contains an array of objects to add
989
- * @param callback (optional) the result callback with two arguments:
990
- * success: boolean set to true if the request was successfull
991
- * content: the server answer that updateAt and taskID
992
- */
993
- addObjects: function(objects, callback) {
994
- var indexObj = this;
995
- var postObj = {requests:[]};
996
- for (var i = 0; i < objects.length; ++i) {
997
- var request = { action: 'addObject',
998
- body: objects[i] };
999
- postObj.requests.push(request);
1000
- }
1001
- return this.as._jsonRequest({ method: 'POST',
1002
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1003
- body: postObj,
1004
- callback: callback });
1005
- },
1006
- /*
1007
- * Get an object from this index
1008
- *
1009
- * @param objectID the unique identifier of the object to retrieve
1010
- * @param callback (optional) the result callback with two arguments
1011
- * success: boolean set to true if the request was successfull
1012
- * content: the object to retrieve or the error message if a failure occured
1013
- * @param attributes (optional) if set, contains the array of attribute names to retrieve
1014
- */
1015
- getObject: function(objectID, callback, attributes) {
1016
- if (Object.prototype.toString.call(callback) === '[object Array]' && !attributes) {
1017
- attributes = callback;
1018
- callback = null;
1019
- }
1020
- var indexObj = this;
1021
- var params = '';
1022
- if (!this.as._isUndefined(attributes)) {
1023
- params = '?attributes=';
1024
- for (var i = 0; i < attributes.length; ++i) {
1025
- if (i !== 0) {
1026
- params += ',';
1027
- }
1028
- params += attributes[i];
1029
- }
1030
- }
1031
- if (this.as.jsonp === null) {
1032
- return this.as._jsonRequest({ method: 'GET',
1033
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params,
1034
- callback: callback });
1035
- } else {
1036
- var pObj = {params: params};
1037
- return this.as._jsonRequest({ method: 'GET',
1038
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
1039
- callback: callback,
1040
- body: pObj});
1041
- }
1042
- },
1043
-
1044
- /*
1045
- * Update partially an object (only update attributes passed in argument)
1046
- *
1047
- * @param partialObject contains the javascript attributes to override, the
1048
- * object must contains an objectID attribute
1049
- * @param callback (optional) the result callback with two arguments:
1050
- * success: boolean set to true if the request was successfull
1051
- * content: the server answer that contains 3 elements: createAt, taskId and objectID
1052
- */
1053
- partialUpdateObject: function(partialObject, callback) {
1054
- var indexObj = this;
1055
- return this.as._jsonRequest({ method: 'POST',
1056
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial',
1057
- body: partialObject,
1058
- callback: callback });
1059
- },
1060
- /*
1061
- * Partially Override the content of several objects
1062
- *
1063
- * @param objects contains an array of objects to update (each object must contains a objectID attribute)
1064
- * @param callback (optional) the result callback with two arguments:
1065
- * success: boolean set to true if the request was successfull
1066
- * content: the server answer that updateAt and taskID
1067
- */
1068
- partialUpdateObjects: function(objects, callback) {
1069
- var indexObj = this;
1070
- var postObj = {requests:[]};
1071
- for (var i = 0; i < objects.length; ++i) {
1072
- var request = { action: 'partialUpdateObject',
1073
- objectID: objects[i].objectID,
1074
- body: objects[i] };
1075
- postObj.requests.push(request);
1076
- }
1077
- return this.as._jsonRequest({ method: 'POST',
1078
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1079
- body: postObj,
1080
- callback: callback });
1081
- },
1082
- /*
1083
- * Override the content of object
1084
- *
1085
- * @param object contains the javascript object to save, the object must contains an objectID attribute
1086
- * @param callback (optional) the result callback with two arguments:
1087
- * success: boolean set to true if the request was successfull
1088
- * content: the server answer that updateAt and taskID
1089
- */
1090
- saveObject: function(object, callback) {
1091
- var indexObj = this;
1092
- return this.as._jsonRequest({ method: 'PUT',
1093
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID),
1094
- body: object,
1095
- callback: callback });
1096
- },
1097
- /*
1098
- * Override the content of several objects
1099
- *
1100
- * @param objects contains an array of objects to update (each object must contains a objectID attribute)
1101
- * @param callback (optional) the result callback with two arguments:
1102
- * success: boolean set to true if the request was successfull
1103
- * content: the server answer that updateAt and taskID
1104
- */
1105
- saveObjects: function(objects, callback) {
1106
- var indexObj = this;
1107
- var postObj = {requests:[]};
1108
- for (var i = 0; i < objects.length; ++i) {
1109
- var request = { action: 'updateObject',
1110
- objectID: objects[i].objectID,
1111
- body: objects[i] };
1112
- postObj.requests.push(request);
1113
- }
1114
- return this.as._jsonRequest({ method: 'POST',
1115
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1116
- body: postObj,
1117
- callback: callback });
1118
- },
1119
- /*
1120
- * Delete an object from the index
1121
- *
1122
- * @param objectID the unique identifier of object to delete
1123
- * @param callback (optional) the result callback with two arguments:
1124
- * success: boolean set to true if the request was successfull
1125
- * content: the server answer that contains 3 elements: createAt, taskId and objectID
1126
- */
1127
- deleteObject: function(objectID, callback) {
1128
- if (objectID === null || objectID.length === 0) {
1129
- callback(false, { message: 'empty objectID'});
1130
- return;
1131
- }
1132
- var indexObj = this;
1133
- return this.as._jsonRequest({ method: 'DELETE',
1134
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
1135
- callback: callback });
1136
- },
1137
- /*
1138
- * Search inside the index using XMLHttpRequest request (Using a POST query to
1139
- * minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
1140
- *
1141
- * @param query the full text query
1142
- * @param callback the result callback with two arguments:
1143
- * success: boolean set to true if the request was successfull. If false, the content contains the error.
1144
- * content: the server answer that contains the list of results.
1145
- * @param args (optional) if set, contains an object with query parameters:
1146
- * - page: (integer) Pagination parameter used to select the page to retrieve.
1147
- * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
1148
- * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20.
1149
- * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size).
1150
- * Attributes are separated with a comma (for example "name,address").
1151
- * You can also use a string array encoding (for example ["name","address"]).
1152
- * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index.
1153
- * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query.
1154
- * Attributes are separated by a comma. You can also use a string array encoding (for example ["name","address"]).
1155
- * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted.
1156
- * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted.
1157
- * A matchLevel is returned for each highlighted attribute and can contain:
1158
- * - full: if all the query terms were found in the attribute,
1159
- * - partial: if only some of the query terms were found,
1160
- * - none: if none of the query terms were found.
1161
- * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`).
1162
- * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10).
1163
- * You can also use a string array encoding (Example: attributesToSnippet: ["name:10","content:10"]). By default no snippet is computed.
1164
- * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3.
1165
- * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7.
1166
- * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute.
1167
- * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma).
1168
- * For example aroundLatLng=47.316669,5.016670).
1169
- * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision
1170
- * (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).
1171
- * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
1172
- * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng).
1173
- * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201).
1174
- * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
1175
- * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma.
1176
- * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`.
1177
- * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000.
1178
- * You can also use a string array encoding (for example numericFilters: ["price>100","price<1000"]).
1179
- * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas.
1180
- * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3).
1181
- * You can also use a string array encoding, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3).
1182
- * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}).
1183
- * - facetFilters: filter the query by a list of facets.
1184
- * Facets are separated by commas and each facet is encoded as `attributeName:value`.
1185
- * For example: `facetFilters=category:Book,author:John%20Doe`.
1186
- * You can also use a string array encoding (for example `["category:Book","author:John%20Doe"]`).
1187
- * - facets: List of object attributes that you want to use for faceting.
1188
- * Attributes are separated with a comma (for example `"category,author"` ).
1189
- * You can also use a JSON string array encoding (for example ["category","author"]).
1190
- * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter.
1191
- * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**.
1192
- * - queryType: select how the query words are interpreted, it can be one of the following value:
1193
- * - prefixAll: all query words are interpreted as prefixes,
1194
- * - prefixLast: only the last word is interpreted as a prefix (default behavior),
1195
- * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
1196
- * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query.
1197
- * The list of words is comma separated.
1198
- * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set.
1199
- * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter,
1200
- * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results.
1201
- * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best
1202
- * one is kept and others are removed.
1203
- * @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime.
1204
- */
1205
- search: function(query, callback, args, delay) {
1206
- if (query === undefined || query === null) {
1207
- query = '';
1208
- }
1051
+ },
1052
+ /*
1053
+ * Add several objects
1054
+ *
1055
+ * @param objects contains an array of objects to add
1056
+ * @param callback (optional) the result callback with two arguments:
1057
+ * success: boolean set to true if the request was successfull
1058
+ * content: the server answer that updateAt and taskID
1059
+ */
1060
+ addObjects: function(objects, callback) {
1061
+ var indexObj = this;
1062
+ var postObj = {requests:[]};
1063
+ for (var i = 0; i < objects.length; ++i) {
1064
+ var request = { action: 'addObject',
1065
+ body: objects[i] };
1066
+ postObj.requests.push(request);
1067
+ }
1068
+ return this.as._jsonRequest({ method: 'POST',
1069
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1070
+ body: postObj,
1071
+ callback: callback });
1072
+ },
1073
+ /*
1074
+ * Get an object from this index
1075
+ *
1076
+ * @param objectID the unique identifier of the object to retrieve
1077
+ * @param callback (optional) the result callback with two arguments
1078
+ * success: boolean set to true if the request was successfull
1079
+ * content: the object to retrieve or the error message if a failure occured
1080
+ * @param attributes (optional) if set, contains the array of attribute names to retrieve
1081
+ */
1082
+ getObject: function(objectID, callback, attributes) {
1083
+ if (Object.prototype.toString.call(callback) === '[object Array]' && !attributes) {
1084
+ attributes = callback;
1085
+ callback = null;
1086
+ }
1087
+ var indexObj = this;
1088
+ var params = '';
1089
+ if (!this.as._isUndefined(attributes)) {
1090
+ params = '?attributes=';
1091
+ for (var i = 0; i < attributes.length; ++i) {
1092
+ if (i !== 0) {
1093
+ params += ',';
1094
+ }
1095
+ params += attributes[i];
1096
+ }
1097
+ }
1209
1098
 
1210
- // no query = getAllObjects
1211
- if (typeof query === 'function') {
1212
- callback = query;
1213
- query = '';
1214
- }
1099
+ return this.as._jsonRequest({ method: 'GET',
1100
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID) + params,
1101
+ callback: callback });
1102
+ },
1103
+
1104
+ /*
1105
+ * Update partially an object (only update attributes passed in argument)
1106
+ *
1107
+ * @param partialObject contains the javascript attributes to override, the
1108
+ * object must contains an objectID attribute
1109
+ * @param callback (optional) the result callback with two arguments:
1110
+ * success: boolean set to true if the request was successfull
1111
+ * content: the server answer that contains 3 elements: createAt, taskId and objectID
1112
+ */
1113
+ partialUpdateObject: function(partialObject, callback) {
1114
+ var indexObj = this;
1115
+ return this.as._jsonRequest({ method: 'POST',
1116
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(partialObject.objectID) + '/partial',
1117
+ body: partialObject,
1118
+ callback: callback });
1119
+ },
1120
+ /*
1121
+ * Partially Override the content of several objects
1122
+ *
1123
+ * @param objects contains an array of objects to update (each object must contains a objectID attribute)
1124
+ * @param callback (optional) the result callback with two arguments:
1125
+ * success: boolean set to true if the request was successfull
1126
+ * content: the server answer that updateAt and taskID
1127
+ */
1128
+ partialUpdateObjects: function(objects, callback) {
1129
+ var indexObj = this;
1130
+ var postObj = {requests:[]};
1131
+ for (var i = 0; i < objects.length; ++i) {
1132
+ var request = { action: 'partialUpdateObject',
1133
+ objectID: objects[i].objectID,
1134
+ body: objects[i] };
1135
+ postObj.requests.push(request);
1136
+ }
1137
+ return this.as._jsonRequest({ method: 'POST',
1138
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1139
+ body: postObj,
1140
+ callback: callback });
1141
+ },
1142
+ /*
1143
+ * Override the content of object
1144
+ *
1145
+ * @param object contains the javascript object to save, the object must contains an objectID attribute
1146
+ * @param callback (optional) the result callback with two arguments:
1147
+ * success: boolean set to true if the request was successfull
1148
+ * content: the server answer that updateAt and taskID
1149
+ */
1150
+ saveObject: function(object, callback) {
1151
+ var indexObj = this;
1152
+ return this.as._jsonRequest({ method: 'PUT',
1153
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(object.objectID),
1154
+ body: object,
1155
+ callback: callback });
1156
+ },
1157
+ /*
1158
+ * Override the content of several objects
1159
+ *
1160
+ * @param objects contains an array of objects to update (each object must contains a objectID attribute)
1161
+ * @param callback (optional) the result callback with two arguments:
1162
+ * success: boolean set to true if the request was successfull
1163
+ * content: the server answer that updateAt and taskID
1164
+ */
1165
+ saveObjects: function(objects, callback) {
1166
+ var indexObj = this;
1167
+ var postObj = {requests:[]};
1168
+ for (var i = 0; i < objects.length; ++i) {
1169
+ var request = { action: 'updateObject',
1170
+ objectID: objects[i].objectID,
1171
+ body: objects[i] };
1172
+ postObj.requests.push(request);
1173
+ }
1174
+ return this.as._jsonRequest({ method: 'POST',
1175
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/batch',
1176
+ body: postObj,
1177
+ callback: callback });
1178
+ },
1179
+ /*
1180
+ * Delete an object from the index
1181
+ *
1182
+ * @param objectID the unique identifier of object to delete
1183
+ * @param callback (optional) the result callback with two arguments:
1184
+ * success: boolean set to true if the request was successfull
1185
+ * content: the server answer that contains 3 elements: createAt, taskId and objectID
1186
+ */
1187
+ deleteObject: function(objectID, callback) {
1188
+ if (objectID === null || objectID.length === 0) {
1189
+ callback(false, { message: 'empty objectID'});
1190
+ return;
1191
+ }
1192
+ var indexObj = this;
1193
+ return this.as._jsonRequest({ method: 'DELETE',
1194
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/' + encodeURIComponent(objectID),
1195
+ callback: callback });
1196
+ },
1197
+ /*
1198
+ * Search inside the index using XMLHttpRequest request (Using a POST query to
1199
+ * minimize number of OPTIONS queries: Cross-Origin Resource Sharing).
1200
+ *
1201
+ * @param query the full text query
1202
+ * @param callback the result callback with two arguments:
1203
+ * success: boolean set to true if the request was successfull. If false, the content contains the error.
1204
+ * content: the server answer that contains the list of results.
1205
+ * @param args (optional) if set, contains an object with query parameters:
1206
+ * - page: (integer) Pagination parameter used to select the page to retrieve.
1207
+ * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
1208
+ * - hitsPerPage: (integer) Pagination parameter used to select the number of hits per page. Defaults to 20.
1209
+ * - attributesToRetrieve: a string that contains the list of object attributes you want to retrieve (let you minimize the answer size).
1210
+ * Attributes are separated with a comma (for example "name,address").
1211
+ * You can also use an array (for example ["name","address"]).
1212
+ * By default, all attributes are retrieved. You can also use '*' to retrieve all values when an attributesToRetrieve setting is specified for your index.
1213
+ * - attributesToHighlight: a string that contains the list of attributes you want to highlight according to the query.
1214
+ * Attributes are separated by a comma. You can also use an array (for example ["name","address"]).
1215
+ * If an attribute has no match for the query, the raw value is returned. By default all indexed text attributes are highlighted.
1216
+ * You can use `*` if you want to highlight all textual attributes. Numerical attributes are not highlighted.
1217
+ * A matchLevel is returned for each highlighted attribute and can contain:
1218
+ * - full: if all the query terms were found in the attribute,
1219
+ * - partial: if only some of the query terms were found,
1220
+ * - none: if none of the query terms were found.
1221
+ * - attributesToSnippet: a string that contains the list of attributes to snippet alongside the number of words to return (syntax is `attributeName:nbWords`).
1222
+ * Attributes are separated by a comma (Example: attributesToSnippet=name:10,content:10).
1223
+ * You can also use an array (Example: attributesToSnippet: ['name:10','content:10']). By default no snippet is computed.
1224
+ * - minWordSizefor1Typo: the minimum number of characters in a query word to accept one typo in this word. Defaults to 3.
1225
+ * - minWordSizefor2Typos: the minimum number of characters in a query word to accept two typos in this word. Defaults to 7.
1226
+ * - getRankingInfo: if set to 1, the result hits will contain ranking information in _rankingInfo attribute.
1227
+ * - aroundLatLng: search for entries around a given latitude/longitude (specified as two floats separated by a comma).
1228
+ * For example aroundLatLng=47.316669,5.016670).
1229
+ * You can specify the maximum distance in meters with the aroundRadius parameter (in meters) and the precision for ranking with aroundPrecision
1230
+ * (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).
1231
+ * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
1232
+ * - insideBoundingBox: search entries inside a given area defined by the two extreme points of a rectangle (defined by 4 floats: p1Lat,p1Lng,p2Lat,p2Lng).
1233
+ * For example insideBoundingBox=47.3165,4.9665,47.3424,5.0201).
1234
+ * At indexing, you should specify geoloc of an object with the _geoloc attribute (in the form {"_geoloc":{"lat":48.853409, "lng":2.348800}})
1235
+ * - numericFilters: a string that contains the list of numeric filters you want to apply separated by a comma.
1236
+ * The syntax of one filter is `attributeName` followed by `operand` followed by `value`. Supported operands are `<`, `<=`, `=`, `>` and `>=`.
1237
+ * You can have multiple conditions on one attribute like for example numericFilters=price>100,price<1000.
1238
+ * You can also use an array (for example numericFilters: ["price>100","price<1000"]).
1239
+ * - tagFilters: filter the query by a set of tags. You can AND tags by separating them by commas.
1240
+ * To OR tags, you must add parentheses. For example, tags=tag1,(tag2,tag3) means tag1 AND (tag2 OR tag3).
1241
+ * You can also use an array, for example tagFilters: ["tag1",["tag2","tag3"]] means tag1 AND (tag2 OR tag3).
1242
+ * At indexing, tags should be added in the _tags** attribute of objects (for example {"_tags":["tag1","tag2"]}).
1243
+ * - facetFilters: filter the query by a list of facets.
1244
+ * Facets are separated by commas and each facet is encoded as `attributeName:value`.
1245
+ * For example: `facetFilters=category:Book,author:John%20Doe`.
1246
+ * You can also use an array (for example `["category:Book","author:John%20Doe"]`).
1247
+ * - facets: List of object attributes that you want to use for faceting.
1248
+ * Comma separated list: `"category,author"` or array `['category','author']`
1249
+ * Only attributes that have been added in **attributesForFaceting** index setting can be used in this parameter.
1250
+ * You can also use `*` to perform faceting on all attributes specified in **attributesForFaceting**.
1251
+ * - queryType: select how the query words are interpreted, it can be one of the following value:
1252
+ * - prefixAll: all query words are interpreted as prefixes,
1253
+ * - prefixLast: only the last word is interpreted as a prefix (default behavior),
1254
+ * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
1255
+ * - optionalWords: a string that contains the list of words that should be considered as optional when found in the query.
1256
+ * Comma separated and array are accepted.
1257
+ * - distinct: If set to 1, enable the distinct feature (disabled by default) if the attributeForDistinct index setting is set.
1258
+ * This feature is similar to the SQL "distinct" keyword: when enabled in a query with the distinct=1 parameter,
1259
+ * all hits containing a duplicate value for the attributeForDistinct attribute are removed from results.
1260
+ * For example, if the chosen attribute is show_name and several hits have the same value for show_name, then only the best
1261
+ * one is kept and others are removed.
1262
+ * - restrictSearchableAttributes: List of attributes you want to use for textual search (must be a subset of the attributesToIndex index setting)
1263
+ * either comma separated or as an array
1264
+ * @param delay (optional) if set, wait for this delay (in ms) and only send the query if there was no other in the meantime.
1265
+ */
1266
+ search: function(query, callback, args, delay) {
1267
+ if (query === undefined || query === null) {
1268
+ query = '';
1269
+ }
1215
1270
 
1216
- if (typeof callback === 'object' && (this.as._isUndefined(args) || !args)) {
1217
- args = callback;
1218
- callback = null;
1219
- }
1271
+ // no query = getAllObjects
1272
+ if (typeof query === 'function') {
1273
+ callback = query;
1274
+ query = '';
1275
+ }
1220
1276
 
1221
- var indexObj = this;
1222
- var params = 'query=' + encodeURIComponent(query);
1223
- if (!this.as._isUndefined(args) && args !== null) {
1224
- params = this.as._getSearchParams(args, params);
1225
- }
1226
- window.clearTimeout(indexObj.onDelayTrigger);
1227
- if (!this.as._isUndefined(delay) && delay !== null && delay > 0) {
1228
- var onDelayTrigger = window.setTimeout( function() {
1229
- indexObj._search(params, callback);
1230
- }, delay);
1231
- indexObj.onDelayTrigger = onDelayTrigger;
1232
- } else {
1233
- return this._search(params, callback);
1234
- }
1235
- },
1236
-
1237
- /*
1238
- * Browse all index content
1239
- *
1240
- * @param page Pagination parameter used to select the page to retrieve.
1241
- * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
1242
- * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000.
1243
- */
1244
- browse: function(page, callback, hitsPerPage) {
1245
- if (+callback > 0 && (this.as._isUndefined(hitsPerPage) || !hitsPerPage)) {
1246
- hitsPerPage = callback;
1247
- callback = null;
1248
- }
1249
- var indexObj = this;
1250
- var params = '?page=' + page;
1251
- if (!this.as._isUndefined(hitsPerPage)) {
1252
- params += '&hitsPerPage=' + hitsPerPage;
1253
- }
1254
- return this.as._jsonRequest({ method: 'GET',
1255
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params,
1256
- callback: callback });
1257
- },
1258
-
1259
- /*
1260
- * Get a Typeahead.js adapter
1261
- * @param searchParams contains an object with query parameters (see search for details)
1262
- */
1263
- ttAdapter: function(params) {
1264
- var self = this;
1265
- return function(query, cb) {
1266
- self.search(query, function(success, content) {
1267
- if (success) {
1268
- cb(content.hits);
1269
- }
1270
- }, params);
1271
- };
1272
- },
1273
-
1274
- /*
1275
- * Wait the publication of a task on the server.
1276
- * All server task are asynchronous and you can check with this method that the task is published.
1277
- *
1278
- * @param taskID the id of the task returned by server
1279
- * @param callback the result callback with with two arguments:
1280
- * success: boolean set to true if the request was successfull
1281
- * content: the server answer that contains the list of results
1282
- */
1283
- waitTask: function(taskID, callback) {
1284
- var indexObj = this;
1285
- return this.as._jsonRequest({ method: 'GET',
1286
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID,
1287
- callback: function(success, body) {
1288
- if (success) {
1289
- if (body.status === 'published') {
1290
- callback(true, body);
1291
- } else {
1292
- setTimeout(function() { indexObj.waitTask(taskID, callback); }, 100);
1293
- }
1294
- } else {
1295
- callback(false, body);
1296
- }
1297
- }});
1298
- },
1299
-
1300
- /*
1301
- * This function deletes the index content. Settings and index specific API keys are kept untouched.
1302
- *
1303
- * @param callback (optional) the result callback with two arguments
1304
- * success: boolean set to true if the request was successfull
1305
- * content: the settings object or the error message if a failure occured
1306
- */
1307
- clearIndex: function(callback) {
1308
- var indexObj = this;
1309
- return this.as._jsonRequest({ method: 'POST',
1310
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear',
1311
- callback: callback });
1312
- },
1313
- /*
1314
- * Get settings of this index
1315
- *
1316
- * @param callback (optional) the result callback with two arguments
1317
- * success: boolean set to true if the request was successfull
1318
- * content: the settings object or the error message if a failure occured
1319
- */
1320
- getSettings: function(callback) {
1321
- var indexObj = this;
1322
- return this.as._jsonRequest({ method: 'GET',
1323
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
1324
- callback: callback });
1325
- },
1326
-
1327
- /*
1328
- * Set settings for this index
1329
- *
1330
- * @param settigns the settings object that can contains :
1331
- * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3).
1332
- * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7).
1333
- * - hitsPerPage: (integer) the number of hits per page (default = 10).
1334
- * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
1335
- * If set to null, all attributes are retrieved.
1336
- * - attributesToHighlight: (array of strings) default list of attributes to highlight.
1337
- * If set to null, all indexed attributes are highlighted.
1338
- * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords).
1339
- * By default no snippet is computed. If set to null, no snippet is computed.
1340
- * - attributesToIndex: (array of strings) the list of fields you want to index.
1341
- * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results.
1342
- * This parameter has two important uses:
1343
- * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to
1344
- * retrieve it but you don't want to search in the base64 string.
1345
- * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of
1346
- * the list will be considered more important than matches in attributes further down the list.
1347
- * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable
1348
- * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"].
1349
- * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
1350
- * 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.
1351
- * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled
1352
- * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results.
1353
- * 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.
1354
- * - ranking: (array of strings) controls the way results are sorted.
1355
- * We have six available criteria:
1356
- * - typo: sort according to number of typos,
1357
- * - geo: sort according to decreassing distance when performing a geo-location based search,
1358
- * - proximity: sort according to the proximity of query words in hits,
1359
- * - attribute: sort according to the order of attributes defined by attributesToIndex,
1360
- * - exact:
1361
- * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others.
1362
- * 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
1363
- * show starting by the v letter before it.
1364
- * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix).
1365
- * - custom: sort according to a user defined formula set in **customRanking** attribute.
1366
- * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
1367
- * - customRanking: (array of strings) lets you specify part of the ranking.
1368
- * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator.
1369
- * For example `"customRanking" => ["desc(population)", "asc(name)"]`
1370
- * - queryType: Select how the query words are interpreted, it can be one of the following value:
1371
- * - prefixAll: all query words are interpreted as prefixes,
1372
- * - prefixLast: only the last word is interpreted as a prefix (default behavior),
1373
- * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
1374
- * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to "<em>").
1375
- * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to "</em>").
1376
- * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query.
1377
- * @param callback (optional) the result callback with two arguments
1378
- * success: boolean set to true if the request was successfull
1379
- * content: the server answer or the error message if a failure occured
1380
- */
1381
- setSettings: function(settings, callback) {
1382
- var indexObj = this;
1383
- return this.as._jsonRequest({ method: 'PUT',
1384
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
1385
- body: settings,
1386
- callback: callback });
1387
- },
1388
- /*
1389
- * List all existing user keys associated to this index
1390
- *
1391
- * @param callback the result callback with two arguments
1392
- * success: boolean set to true if the request was successfull
1393
- * content: the server answer with user keys list or error description if success is false.
1394
- */
1395
- listUserKeys: function(callback) {
1396
- var indexObj = this;
1397
- return this.as._jsonRequest({ method: 'GET',
1398
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1399
- callback: callback });
1400
- },
1401
- /*
1402
- * Get ACL of a user key associated to this index
1403
- *
1404
- * @param callback the result callback with two arguments
1405
- * success: boolean set to true if the request was successfull
1406
- * content: the server answer with user keys list or error description if success is false.
1407
- */
1408
- getUserKeyACL: function(key, callback) {
1409
- var indexObj = this;
1410
- return this.as._jsonRequest({ method: 'GET',
1411
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
1412
- callback: callback });
1413
- },
1414
- /*
1415
- * Delete an existing user key associated to this index
1416
- *
1417
- * @param callback the result callback with two arguments
1418
- * success: boolean set to true if the request was successfull
1419
- * content: the server answer with user keys list or error description if success is false.
1420
- */
1421
- deleteUserKey: function(key, callback) {
1422
- var indexObj = this;
1423
- return this.as._jsonRequest({ method: 'DELETE',
1424
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
1425
- callback: callback });
1426
- },
1427
- /*
1428
- * Add an existing user key associated to this index
1429
- *
1430
- * @param acls the list of ACL for this key. Defined by an array of strings that
1431
- * can contains the following values:
1432
- * - search: allow to search (https and http)
1433
- * - addObject: allows to add/update an object in the index (https only)
1434
- * - deleteObject : allows to delete an existing object (https only)
1435
- * - deleteIndex : allows to delete index content (https only)
1436
- * - settings : allows to get index settings (https only)
1437
- * - editSettings : allows to change index settings (https only)
1438
- * @param callback the result callback with two arguments
1439
- * success: boolean set to true if the request was successfull
1440
- * content: the server answer with user keys list or error description if success is false.
1441
- */
1442
- addUserKey: function(acls, callback) {
1443
- var indexObj = this;
1444
- var aclsObject = {};
1445
- aclsObject.acl = acls;
1446
- return this.as._jsonRequest({ method: 'POST',
1447
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1448
- body: aclsObject,
1449
- callback: callback });
1450
- },
1451
- /*
1452
- * Add an existing user key associated to this index
1453
- *
1454
- * @param acls the list of ACL for this key. Defined by an array of strings that
1455
- * can contains the following values:
1456
- * - search: allow to search (https and http)
1457
- * - addObject: allows to add/update an object in the index (https only)
1458
- * - deleteObject : allows to delete an existing object (https only)
1459
- * - deleteIndex : allows to delete index content (https only)
1460
- * - settings : allows to get index settings (https only)
1461
- * - editSettings : allows to change index settings (https only)
1462
- * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
1463
- * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
1464
- * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
1465
- * @param callback the result callback with two arguments
1466
- * success: boolean set to true if the request was successfull
1467
- * content: the server answer with user keys list or error description if success is false.
1468
- */
1469
- addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
1470
- var indexObj = this;
1471
- var aclsObject = {};
1472
- aclsObject.acl = acls;
1473
- aclsObject.validity = validity;
1474
- aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
1475
- aclsObject.maxHitsPerQuery = maxHitsPerQuery;
1476
- return this.as._jsonRequest({ method: 'POST',
1477
- url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1478
- body: aclsObject,
1479
- callback: callback });
1480
- },
1481
- ///
1482
- /// Internal methods only after this line
1483
- ///
1484
- _search: function(params, callback) {
1485
- var pObj = {params: params};
1486
- if (this.as.jsonp === null) {
1487
- var self = this;
1488
- return this.as._jsonRequest({ cache: this.cache,
1489
- method: 'POST',
1490
- url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
1491
- body: pObj,
1492
- callback: function(success, content) {
1493
- if (!success) {
1494
- // retry first with JSONP
1495
- self.as.jsonp = true;
1496
- self._search(params, callback);
1497
- } else {
1498
- self.as.jsonp = false;
1499
- callback && callback(success, content);
1500
- }
1501
- }
1502
- });
1503
- } else if (this.as.jsonp) {
1504
- return this.as._jsonRequest({ cache: this.cache,
1505
- method: 'GET',
1506
- url: '/1/indexes/' + encodeURIComponent(this.indexName),
1507
- body: pObj,
1508
- callback: callback });
1509
- } else {
1510
- return this.as._jsonRequest({ cache: this.cache,
1511
- method: 'POST',
1512
- url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
1513
- body: pObj,
1514
- callback: callback});
1515
- }
1516
- },
1277
+ if (typeof callback === 'object' && (this.as._isUndefined(args) || !args)) {
1278
+ args = callback;
1279
+ callback = null;
1280
+ }
1281
+
1282
+ var indexObj = this;
1283
+ var params = 'query=' + encodeURIComponent(query);
1284
+ if (!this.as._isUndefined(args) && args !== null) {
1285
+ params = this.as._getSearchParams(args, params);
1286
+ }
1287
+ window.clearTimeout(indexObj.onDelayTrigger);
1288
+ if (!this.as._isUndefined(delay) && delay !== null && delay > 0) {
1289
+ var onDelayTrigger = window.setTimeout( function() {
1290
+ indexObj._search(params, callback);
1291
+ }, delay);
1292
+ indexObj.onDelayTrigger = onDelayTrigger;
1293
+ } else {
1294
+ return this._search(params, callback);
1295
+ }
1296
+ },
1297
+
1298
+ /*
1299
+ * Browse all index content
1300
+ *
1301
+ * @param page Pagination parameter used to select the page to retrieve.
1302
+ * Page is zero-based and defaults to 0. Thus, to retrieve the 10th page you need to set page=9
1303
+ * @param hitsPerPage: Pagination parameter used to select the number of hits per page. Defaults to 1000.
1304
+ */
1305
+ browse: function(page, callback, hitsPerPage) {
1306
+ if (+callback > 0 && (this.as._isUndefined(hitsPerPage) || !hitsPerPage)) {
1307
+ hitsPerPage = callback;
1308
+ callback = null;
1309
+ }
1310
+ var indexObj = this;
1311
+ var params = '?page=' + page;
1312
+ if (!this.as._isUndefined(hitsPerPage)) {
1313
+ params += '&hitsPerPage=' + hitsPerPage;
1314
+ }
1315
+ return this.as._jsonRequest({ method: 'GET',
1316
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/browse' + params,
1317
+ callback: callback });
1318
+ },
1319
+
1320
+ /*
1321
+ * Get a Typeahead.js adapter
1322
+ * @param searchParams contains an object with query parameters (see search for details)
1323
+ */
1324
+ ttAdapter: function(params) {
1325
+ var self = this;
1326
+ return function(query, cb) {
1327
+ self.search(query, function(success, content) {
1328
+ if (success) {
1329
+ cb(content.hits);
1330
+ } else {
1331
+ cb(content && content.message);
1332
+ }
1333
+ }, params);
1334
+ };
1335
+ },
1336
+
1337
+ /*
1338
+ * Wait the publication of a task on the server.
1339
+ * All server task are asynchronous and you can check with this method that the task is published.
1340
+ *
1341
+ * @param taskID the id of the task returned by server
1342
+ * @param callback the result callback with with two arguments:
1343
+ * success: boolean set to true if the request was successfull
1344
+ * content: the server answer that contains the list of results
1345
+ */
1346
+ waitTask: function(taskID, callback) {
1347
+ var indexObj = this;
1348
+ return this.as._jsonRequest({ method: 'GET',
1349
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/task/' + taskID,
1350
+ callback: function(success, body) {
1351
+ if (success) {
1352
+ if (body.status === 'published') {
1353
+ callback(true, body);
1354
+ } else {
1355
+ setTimeout(function() { indexObj.waitTask(taskID, callback); }, 100);
1356
+ }
1357
+ } else {
1358
+ callback(false, body);
1359
+ }
1360
+ }});
1361
+ },
1362
+
1363
+ /*
1364
+ * This function deletes the index content. Settings and index specific API keys are kept untouched.
1365
+ *
1366
+ * @param callback (optional) the result callback with two arguments
1367
+ * success: boolean set to true if the request was successfull
1368
+ * content: the settings object or the error message if a failure occured
1369
+ */
1370
+ clearIndex: function(callback) {
1371
+ var indexObj = this;
1372
+ return this.as._jsonRequest({ method: 'POST',
1373
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/clear',
1374
+ callback: callback });
1375
+ },
1376
+ /*
1377
+ * Get settings of this index
1378
+ *
1379
+ * @param callback (optional) the result callback with two arguments
1380
+ * success: boolean set to true if the request was successfull
1381
+ * content: the settings object or the error message if a failure occured
1382
+ */
1383
+ getSettings: function(callback) {
1384
+ var indexObj = this;
1385
+ return this.as._jsonRequest({ method: 'GET',
1386
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
1387
+ callback: callback });
1388
+ },
1389
+
1390
+ /*
1391
+ * Set settings for this index
1392
+ *
1393
+ * @param settigns the settings object that can contains :
1394
+ * - minWordSizefor1Typo: (integer) the minimum number of characters to accept one typo (default = 3).
1395
+ * - minWordSizefor2Typos: (integer) the minimum number of characters to accept two typos (default = 7).
1396
+ * - hitsPerPage: (integer) the number of hits per page (default = 10).
1397
+ * - attributesToRetrieve: (array of strings) default list of attributes to retrieve in objects.
1398
+ * If set to null, all attributes are retrieved.
1399
+ * - attributesToHighlight: (array of strings) default list of attributes to highlight.
1400
+ * If set to null, all indexed attributes are highlighted.
1401
+ * - attributesToSnippet**: (array of strings) default list of attributes to snippet alongside the number of words to return (syntax is attributeName:nbWords).
1402
+ * By default no snippet is computed. If set to null, no snippet is computed.
1403
+ * - attributesToIndex: (array of strings) the list of fields you want to index.
1404
+ * If set to null, all textual and numerical attributes of your objects are indexed, but you should update it to get optimal results.
1405
+ * This parameter has two important uses:
1406
+ * - Limit the attributes to index: For example if you store a binary image in base64, you want to store it and be able to
1407
+ * retrieve it but you don't want to search in the base64 string.
1408
+ * - Control part of the ranking*: (see the ranking parameter for full explanation) Matches in attributes at the beginning of
1409
+ * the list will be considered more important than matches in attributes further down the list.
1410
+ * In one attribute, matching text at the beginning of the attribute will be considered more important than text after, you can disable
1411
+ * this behavior if you add your attribute inside `unordered(AttributeName)`, for example attributesToIndex: ["title", "unordered(text)"].
1412
+ * - attributesForFaceting: (array of strings) The list of fields you want to use for faceting.
1413
+ * 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.
1414
+ * - attributeForDistinct: (string) The attribute name used for the Distinct feature. This feature is similar to the SQL "distinct" keyword: when enabled
1415
+ * in query with the distinct=1 parameter, all hits containing a duplicate value for this attribute are removed from results.
1416
+ * 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.
1417
+ * - ranking: (array of strings) controls the way results are sorted.
1418
+ * We have six available criteria:
1419
+ * - typo: sort according to number of typos,
1420
+ * - geo: sort according to decreassing distance when performing a geo-location based search,
1421
+ * - proximity: sort according to the proximity of query words in hits,
1422
+ * - attribute: sort according to the order of attributes defined by attributesToIndex,
1423
+ * - exact:
1424
+ * - if the user query contains one word: sort objects having an attribute that is exactly the query word before others.
1425
+ * 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
1426
+ * show starting by the v letter before it.
1427
+ * - if the user query contains multiple words: sort according to the number of words that matched exactly (and not as a prefix).
1428
+ * - custom: sort according to a user defined formula set in **customRanking** attribute.
1429
+ * The standard order is ["typo", "geo", "proximity", "attribute", "exact", "custom"]
1430
+ * - customRanking: (array of strings) lets you specify part of the ranking.
1431
+ * The syntax of this condition is an array of strings containing attributes prefixed by asc (ascending order) or desc (descending order) operator.
1432
+ * For example `"customRanking" => ["desc(population)", "asc(name)"]`
1433
+ * - queryType: Select how the query words are interpreted, it can be one of the following value:
1434
+ * - prefixAll: all query words are interpreted as prefixes,
1435
+ * - prefixLast: only the last word is interpreted as a prefix (default behavior),
1436
+ * - prefixNone: no query word is interpreted as a prefix. This option is not recommended.
1437
+ * - highlightPreTag: (string) Specify the string that is inserted before the highlighted parts in the query result (default to "<em>").
1438
+ * - highlightPostTag: (string) Specify the string that is inserted after the highlighted parts in the query result (default to "</em>").
1439
+ * - optionalWords: (array of strings) Specify a list of words that should be considered as optional when found in the query.
1440
+ * @param callback (optional) the result callback with two arguments
1441
+ * success: boolean set to true if the request was successfull
1442
+ * content: the server answer or the error message if a failure occured
1443
+ */
1444
+ setSettings: function(settings, callback) {
1445
+ var indexObj = this;
1446
+ return this.as._jsonRequest({ method: 'PUT',
1447
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/settings',
1448
+ body: settings,
1449
+ callback: callback });
1450
+ },
1451
+ /*
1452
+ * List all existing user keys associated to this index
1453
+ *
1454
+ * @param callback the result callback with two arguments
1455
+ * success: boolean set to true if the request was successfull
1456
+ * content: the server answer with user keys list or error description if success is false.
1457
+ */
1458
+ listUserKeys: function(callback) {
1459
+ var indexObj = this;
1460
+ return this.as._jsonRequest({ method: 'GET',
1461
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1462
+ callback: callback });
1463
+ },
1464
+ /*
1465
+ * Get ACL of a user key associated to this index
1466
+ *
1467
+ * @param callback the result callback with two arguments
1468
+ * success: boolean set to true if the request was successfull
1469
+ * content: the server answer with user keys list or error description if success is false.
1470
+ */
1471
+ getUserKeyACL: function(key, callback) {
1472
+ var indexObj = this;
1473
+ return this.as._jsonRequest({ method: 'GET',
1474
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
1475
+ callback: callback });
1476
+ },
1477
+ /*
1478
+ * Delete an existing user key associated to this index
1479
+ *
1480
+ * @param callback the result callback with two arguments
1481
+ * success: boolean set to true if the request was successfull
1482
+ * content: the server answer with user keys list or error description if success is false.
1483
+ */
1484
+ deleteUserKey: function(key, callback) {
1485
+ var indexObj = this;
1486
+ return this.as._jsonRequest({ method: 'DELETE',
1487
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys/' + key,
1488
+ callback: callback });
1489
+ },
1490
+ /*
1491
+ * Add an existing user key associated to this index
1492
+ *
1493
+ * @param acls the list of ACL for this key. Defined by an array of strings that
1494
+ * can contains the following values:
1495
+ * - search: allow to search (https and http)
1496
+ * - addObject: allows to add/update an object in the index (https only)
1497
+ * - deleteObject : allows to delete an existing object (https only)
1498
+ * - deleteIndex : allows to delete index content (https only)
1499
+ * - settings : allows to get index settings (https only)
1500
+ * - editSettings : allows to change index settings (https only)
1501
+ * @param callback the result callback with two arguments
1502
+ * success: boolean set to true if the request was successfull
1503
+ * content: the server answer with user keys list or error description if success is false.
1504
+ */
1505
+ addUserKey: function(acls, callback) {
1506
+ var indexObj = this;
1507
+ var aclsObject = {};
1508
+ aclsObject.acl = acls;
1509
+ return this.as._jsonRequest({ method: 'POST',
1510
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1511
+ body: aclsObject,
1512
+ callback: callback });
1513
+ },
1514
+ /*
1515
+ * Add an existing user key associated to this index
1516
+ *
1517
+ * @param acls the list of ACL for this key. Defined by an array of strings that
1518
+ * can contains the following values:
1519
+ * - search: allow to search (https and http)
1520
+ * - addObject: allows to add/update an object in the index (https only)
1521
+ * - deleteObject : allows to delete an existing object (https only)
1522
+ * - deleteIndex : allows to delete index content (https only)
1523
+ * - settings : allows to get index settings (https only)
1524
+ * - editSettings : allows to change index settings (https only)
1525
+ * @param validity the number of seconds after which the key will be automatically removed (0 means no time limit for this key)
1526
+ * @param maxQueriesPerIPPerHour Specify the maximum number of API calls allowed from an IP address per hour.
1527
+ * @param maxHitsPerQuery Specify the maximum number of hits this API key can retrieve in one call.
1528
+ * @param callback the result callback with two arguments
1529
+ * success: boolean set to true if the request was successfull
1530
+ * content: the server answer with user keys list or error description if success is false.
1531
+ */
1532
+ addUserKeyWithValidity: function(acls, validity, maxQueriesPerIPPerHour, maxHitsPerQuery, callback) {
1533
+ var indexObj = this;
1534
+ var aclsObject = {};
1535
+ aclsObject.acl = acls;
1536
+ aclsObject.validity = validity;
1537
+ aclsObject.maxQueriesPerIPPerHour = maxQueriesPerIPPerHour;
1538
+ aclsObject.maxHitsPerQuery = maxHitsPerQuery;
1539
+ return this.as._jsonRequest({ method: 'POST',
1540
+ url: '/1/indexes/' + encodeURIComponent(indexObj.indexName) + '/keys',
1541
+ body: aclsObject,
1542
+ callback: callback });
1543
+ },
1544
+ ///
1545
+ /// Internal methods only after this line
1546
+ ///
1547
+ _search: function(params, callback) {
1548
+ var pObj = {params: params};
1549
+ if (this.as.jsonp === null) {
1550
+ var self = this;
1551
+ return this.as._jsonRequest({ cache: this.cache,
1552
+ method: 'POST',
1553
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
1554
+ body: pObj,
1555
+ callback: function(success, content) {
1556
+ var status = content && content.status;
1557
+ if (success || status && Math.floor(status / 100) === 4 || Math.floor(status / 100) === 1) {
1558
+ self.as.jsonp = false;
1559
+ callback && callback(success, content);
1560
+ } else {
1561
+ self.as.jsonp = true;
1562
+ self._search(params, callback);
1563
+ }
1564
+ }
1565
+ });
1566
+ } else if (this.as.jsonp) {
1567
+ return this.as._jsonRequest({ cache: this.cache,
1568
+ method: 'GET',
1569
+ url: '/1/indexes/' + encodeURIComponent(this.indexName),
1570
+ body: pObj,
1571
+ callback: callback });
1572
+ } else {
1573
+ return this.as._jsonRequest({ cache: this.cache,
1574
+ method: 'POST',
1575
+ url: '/1/indexes/' + encodeURIComponent(this.indexName) + '/query',
1576
+ body: pObj,
1577
+ callback: callback});
1578
+ }
1579
+ },
1517
1580
 
1518
- // internal attributes
1519
- as: null,
1520
- indexName: null,
1521
- typeAheadArgs: null,
1522
- typeAheadValueOption: null
1581
+ // internal attributes
1582
+ as: null,
1583
+ indexName: null,
1584
+ typeAheadArgs: null,
1585
+ typeAheadValueOption: null
1523
1586
  };
1524
1587
 
1525
1588
  /*
@@ -1876,7 +1939,7 @@ AlgoliaSearch.prototype.Index.prototype = {
1876
1939
  }
1877
1940
  var aggregatedAnswer = content.results[0];
1878
1941
  aggregatedAnswer.disjunctiveFacets = aggregatedAnswer.disjunctiveFacets || {};
1879
- aggregatedAnswer.facetStats = aggregatedAnswer.facetStats || {};
1942
+ aggregatedAnswer.facets_stats = aggregatedAnswer.facets_stats || {};
1880
1943
  // create disjunctive facets from facets (disjunctive facets without refinements)
1881
1944
  for (var facet in unusedDisjunctiveFacets) {
1882
1945
  if (aggregatedAnswer.facets[facet] && !aggregatedAnswer.disjunctiveFacets[facet]) {
@@ -1903,9 +1966,13 @@ AlgoliaSearch.prototype.Index.prototype = {
1903
1966
  }
1904
1967
  // aggregate the disjunctive facets stats
1905
1968
  for (var stats in content.results[i + 1].facets_stats) {
1906
- aggregatedAnswer.facetStats[stats] = content.results[i + 1].facets_stats[stats];
1969
+ aggregatedAnswer.facets_stats[stats] = content.results[i + 1].facets_stats[stats];
1907
1970
  }
1908
1971
  }
1972
+
1973
+ // Backward compatibility
1974
+ aggregatedAnswer.facetStats = aggregatedAnswer.facets_stats;
1975
+
1909
1976
  // add the excludes
1910
1977
  for (var exclude in self.excludes) {
1911
1978
  if (self.excludes[exclude]) {
@@ -1969,7 +2036,8 @@ AlgoliaSearch.prototype.Index.prototype = {
1969
2036
  attributesToHighlight: [],
1970
2037
  attributesToSnippet: [],
1971
2038
  facets: facet,
1972
- facetFilters: this._getFacetFilters(facet)
2039
+ facetFilters: this._getFacetFilters(facet),
2040
+ analytics: false
1973
2041
  });
1974
2042
  },
1975
2043