algoliasearch-rails 1.11.17 → 1.11.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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")})}();
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")})}();
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * typeahead.js 0.10.4
3
+ * https://github.com/twitter/typeahead.js
4
+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5
+ */
6
+
7
+ !function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}}}(),c="0.10.4",d=function(){"use strict";function a(a){return a=b.toStr(a),a?a.split(/\s+/):[]}function c(a){return a=b.toStr(a),a?a.split(/\W+/):[]}function d(a){return function(){var c=[].slice.call(arguments,0);return function(d){var e=[];return b.each(c,function(c){e=e.concat(a(b.toStr(d[c])))}),e}}}return{nonword:c,whitespace:a,obj:{nonword:d(c),whitespace:d(a)}}}(),e=function(){"use strict";function c(c){this.maxSize=b.isNumber(c)?c:100,this.reset(),this.maxSize<=0&&(this.set=this.get=a.noop)}function d(){this.head=this.tail=null}function e(a,b){this.key=a,this.val=b,this.prev=this.next=null}return b.mixin(c.prototype,{set:function(a,b){var c,d=this.list.tail;this.size>=this.maxSize&&(this.list.remove(d),delete this.hash[d.key]),(c=this.hash[a])?(c.val=b,this.list.moveToFront(c)):(c=new e(a,b),this.list.add(c),this.hash[a]=c,this.size++)},get:function(a){var b=this.hash[a];return b?(this.list.moveToFront(b),b.val):void 0},reset:function(){this.size=0,this.hash={},this.list=new d}}),b.mixin(d.prototype,{add:function(a){this.head&&(a.next=this.head,this.head.prev=a),this.head=a,this.tail=this.tail||a},remove:function(a){a.prev?a.prev.next=a.next:this.head=a.next,a.next?a.next.prev=a.prev:this.tail=a.prev},moveToFront:function(a){this.remove(a),this.add(a)}}),c}(),f=function(){"use strict";function a(a){this.prefix=["__",a,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+b.escapeRegExChars(this.prefix))}function c(){return(new Date).getTime()}function d(a){return JSON.stringify(b.isUndefined(a)?null:a)}function e(a){return JSON.parse(a)}var f,g;try{f=window.localStorage,f.setItem("~~~","!"),f.removeItem("~~~")}catch(h){f=null}return g=f&&window.JSON?{_prefix:function(a){return this.prefix+a},_ttlKey:function(a){return this._prefix(a)+this.ttlKey},get:function(a){return this.isExpired(a)&&this.remove(a),e(f.getItem(this._prefix(a)))},set:function(a,e,g){return b.isNumber(g)?f.setItem(this._ttlKey(a),d(c()+g)):f.removeItem(this._ttlKey(a)),f.setItem(this._prefix(a),d(e))},remove:function(a){return f.removeItem(this._ttlKey(a)),f.removeItem(this._prefix(a)),this},clear:function(){var a,b,c=[],d=f.length;for(a=0;d>a;a++)(b=f.key(a)).match(this.keyMatcher)&&c.push(b.replace(this.keyMatcher,""));for(a=c.length;a--;)this.remove(c[a]);return this},isExpired:function(a){var d=e(f.getItem(this._ttlKey(a)));return b.isNumber(d)&&c()>d?!0:!1}}:{get:b.noop,set:b.noop,remove:b.noop,clear:b.noop,isExpired:b.noop},b.mixin(a.prototype,g),a}(),g=function(){"use strict";function c(b){b=b||{},this.cancelled=!1,this.lastUrl=null,this._send=b.transport?d(b.transport):a.ajax,this._get=b.rateLimiter?b.rateLimiter(this._get):this._get,this._cache=b.cache===!1?new e(0):i}function d(c){return function(d,e){function f(a){b.defer(function(){h.resolve(a)})}function g(a){b.defer(function(){h.reject(a)})}var h=a.Deferred();return c(d,e,f,g),h}}var f=0,g={},h=6,i=new e(10);return c.setMaxPendingRequests=function(a){h=a},c.resetCache=function(){i.reset()},b.mixin(c.prototype,{_get:function(a,b,c){function d(b){c&&c(null,b),k._cache.set(a,b)}function e(){c&&c(!0)}function i(){f--,delete g[a],k.onDeckRequestArgs&&(k._get.apply(k,k.onDeckRequestArgs),k.onDeckRequestArgs=null)}var j,k=this;this.cancelled||a!==this.lastUrl||((j=g[a])?j.done(d).fail(e):h>f?(f++,g[a]=this._send(a,b).done(d).fail(e).always(i)):this.onDeckRequestArgs=[].slice.call(arguments,0))},get:function(a,c,d){var e;return b.isFunction(c)&&(d=c,c={}),this.cancelled=!1,this.lastUrl=a,(e=this._cache.get(a))?b.defer(function(){d&&d(null,e)}):this._get(a,c,d),!!e},cancel:function(){this.cancelled=!0}}),c}(),h=function(){"use strict";function c(b){b=b||{},b.datumTokenizer&&b.queryTokenizer||a.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=b.datumTokenizer,this.queryTokenizer=b.queryTokenizer,this.reset()}function d(a){return a=b.filter(a,function(a){return!!a}),a=b.map(a,function(a){return a.toLowerCase()})}function e(){return{ids:[],children:{}}}function f(a){for(var b={},c=[],d=0,e=a.length;e>d;d++)b[a[d]]||(b[a[d]]=!0,c.push(a[d]));return c}function g(a,b){function c(a,b){return a-b}var d=0,e=0,f=[];a=a.sort(c),b=b.sort(c);for(var g=a.length,h=b.length;g>d&&h>e;)a[d]<b[e]?d++:a[d]>b[e]?e++:(f.push(a[d]),d++,e++);return f}return b.mixin(c.prototype,{bootstrap:function(a){this.datums=a.datums,this.trie=a.trie},add:function(a){var c=this;a=b.isArray(a)?a:[a],b.each(a,function(a){var f,g;f=c.datums.push(a)-1,g=d(c.datumTokenizer(a)),b.each(g,function(a){var b,d,g;for(b=c.trie,d=a.split("");g=d.shift();)b=b.children[g]||(b.children[g]=e()),b.ids.push(f)})})},get:function(a){var c,e,h=this;return c=d(this.queryTokenizer(a)),b.each(c,function(a){var b,c,d,f;if(e&&0===e.length)return!1;for(b=h.trie,c=a.split("");b&&(d=c.shift());)b=b.children[d];return b&&0===c.length?(f=b.ids.slice(0),void(e=e?g(e,f):f)):(e=[],!1)}),e?b.map(f(e),function(a){return h.datums[a]}):[]},reset:function(){this.datums=[],this.trie=e()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),c}(),i=function(){"use strict";function d(a){return a.local||null}function e(d){var e,f;return f={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(e=d.prefetch||null)&&(e=b.isString(e)?{url:e}:e,e=b.mixin(f,e),e.thumbprint=c+e.thumbprint,e.ajax.type=e.ajax.type||"GET",e.ajax.dataType=e.ajax.dataType||"json",!e.url&&a.error("prefetch requires url to be set")),e}function f(c){function d(a){return function(c){return b.debounce(c,a)}}function e(a){return function(c){return b.throttle(c,a)}}var f,g;return g={url:null,cache:!0,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(f=c.remote||null)&&(f=b.isString(f)?{url:f}:f,f=b.mixin(g,f),f.rateLimiter=/^throttle$/i.test(f.rateLimitBy)?e(f.rateLimitWait):d(f.rateLimitWait),f.ajax.type=f.ajax.type||"GET",f.ajax.dataType=f.ajax.dataType||"json",delete f.rateLimitBy,delete f.rateLimitWait,!f.url&&a.error("remote requires url to be set")),f}return{local:d,prefetch:e,remote:f}}();!function(c){"use strict";function e(b){b&&(b.local||b.prefetch||b.remote)||a.error("one of local, prefetch, or remote is required"),this.limit=b.limit||5,this.sorter=j(b.sorter),this.dupDetector=b.dupDetector||k,this.local=i.local(b),this.prefetch=i.prefetch(b),this.remote=i.remote(b),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new h({datumTokenizer:b.datumTokenizer,queryTokenizer:b.queryTokenizer}),this.storage=this.cacheKey?new f(this.cacheKey):null}function j(a){function c(b){return b.sort(a)}function d(a){return a}return b.isFunction(a)?c:d}function k(){return!1}var l,m;return l=c.Bloodhound,m={data:"data",protocol:"protocol",thumbprint:"thumbprint"},c.Bloodhound=e,e.noConflict=function(){return c.Bloodhound=l,e},e.tokenizers=d,b.mixin(e.prototype,{_loadPrefetch:function(b){function c(a){f.clear(),f.add(b.filter?b.filter(a):a),f._saveToStorage(f.index.serialize(),b.thumbprint,b.ttl)}var d,e,f=this;return(d=this._readFromStorage(b.thumbprint))?(this.index.bootstrap(d),e=a.Deferred().resolve()):e=a.ajax(b.url,b.ajax).done(c),e},_getFromRemote:function(a,b){function c(a,c){b(a?[]:f.remote.filter?f.remote.filter(c):c)}var d,e,f=this;if(this.transport)return a=a||"",e=encodeURIComponent(a),d=this.remote.replace?this.remote.replace(this.remote.url,a):this.remote.url.replace(this.remote.wildcard,e),this.transport.get(d,this.remote.ajax,c)},_cancelLastRemoteRequest:function(){this.transport&&this.transport.cancel()},_saveToStorage:function(a,b,c){this.storage&&(this.storage.set(m.data,a,c),this.storage.set(m.protocol,location.protocol,c),this.storage.set(m.thumbprint,b,c))},_readFromStorage:function(a){var b,c={};return this.storage&&(c.data=this.storage.get(m.data),c.protocol=this.storage.get(m.protocol),c.thumbprint=this.storage.get(m.thumbprint)),b=c.thumbprint!==a||c.protocol!==location.protocol,c.data&&!b?c.data:null},_initialize:function(){function c(){e.add(b.isFunction(f)?f():f)}var d,e=this,f=this.local;return d=this.prefetch?this._loadPrefetch(this.prefetch):a.Deferred().resolve(),f&&d.done(c),this.transport=this.remote?new g(this.remote):null,this.initPromise=d.promise()},initialize:function(a){return!this.initPromise||a?this._initialize():this.initPromise},add:function(a){this.index.add(a)},get:function(a,c){function d(a){var d=f.slice(0);b.each(a,function(a){var c;return c=b.some(d,function(b){return e.dupDetector(a,b)}),!c&&d.push(a),d.length<e.limit}),c&&c(e.sorter(d))}var e=this,f=[],g=!1;f=this.index.get(a),f=this.sorter(f).slice(0,this.limit),f.length<this.limit?g=this._getFromRemote(a,d):this._cancelLastRemoteRequest(),g||(f.length>0||!this.transport)&&c&&c(f)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&g.resetCache()},ttAdapter:function(){return b.bind(this.get,this)}}),e}(this)}(window.jQuery);
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * typeahead.js 0.10.4
3
+ * https://github.com/twitter/typeahead.js
4
+ * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
5
+ */
6
+
7
+ !function(a){var b=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(a){return!a||/^\s*$/.test(a)},escapeRegExChars:function(a){return a.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(a){return"string"==typeof a},isNumber:function(a){return"number"==typeof a},isArray:a.isArray,isFunction:a.isFunction,isObject:a.isPlainObject,isUndefined:function(a){return"undefined"==typeof a},toStr:function(a){return b.isUndefined(a)||null===a?"":a+""},bind:a.proxy,each:function(b,c){function d(a,b){return c(b,a)}a.each(b,d)},map:a.map,filter:a.grep,every:function(b,c){var d=!0;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?void 0:!1}),!!d):d},some:function(b,c){var d=!1;return b?(a.each(b,function(a,e){return(d=c.call(null,e,a,b))?!1:void 0}),!!d):d},mixin:a.extend,getUniqueId:function(){var a=0;return function(){return a++}}(),templatify:function(b){function c(){return String(b)}return a.isFunction(b)?b:c},defer:function(a){setTimeout(a,0)},debounce:function(a,b,c){var d,e;return function(){var f,g,h=this,i=arguments;return f=function(){d=null,c||(e=a.apply(h,i))},g=c&&!d,clearTimeout(d),d=setTimeout(f,b),g&&(e=a.apply(h,i)),e}},throttle:function(a,b){var c,d,e,f,g,h;return g=0,h=function(){g=new Date,e=null,f=a.apply(c,d)},function(){var i=new Date,j=b-(i-g);return c=this,d=arguments,0>=j?(clearTimeout(e),e=null,g=i,f=a.apply(c,d)):e||(e=setTimeout(h,j)),f}},noop:function(){}}}(),c=function(){return{wrapper:'<span class="twitter-typeahead"></span>',dropdown:'<span class="tt-dropdown-menu"></span>',dataset:'<div class="tt-dataset-%CLASS%"></div>',suggestions:'<span class="tt-suggestions"></span>',suggestion:'<div class="tt-suggestion"></div>'}}(),d=function(){"use strict";var a={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none",opacity:"1"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};return b.isMsie()&&b.mixin(a.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),b.isMsie()&&b.isMsie()<=7&&b.mixin(a.input,{marginTop:"-1px"}),a}(),e=function(){"use strict";function c(b){b&&b.el||a.error("EventBus initialized without el"),this.$el=a(b.el)}var d="typeahead:";return b.mixin(c.prototype,{trigger:function(a){var b=[].slice.call(arguments,1);this.$el.trigger(d+a,b)}}),c}(),f=function(){"use strict";function a(a,b,c,d){var e;if(!c)return this;for(b=b.split(i),c=d?h(c,d):c,this._callbacks=this._callbacks||{};e=b.shift();)this._callbacks[e]=this._callbacks[e]||{sync:[],async:[]},this._callbacks[e][a].push(c);return this}function b(b,c,d){return a.call(this,"async",b,c,d)}function c(b,c,d){return a.call(this,"sync",b,c,d)}function d(a){var b;if(!this._callbacks)return this;for(a=a.split(i);b=a.shift();)delete this._callbacks[b];return this}function e(a){var b,c,d,e,g;if(!this._callbacks)return this;for(a=a.split(i),d=[].slice.call(arguments,1);(b=a.shift())&&(c=this._callbacks[b]);)e=f(c.sync,this,[b].concat(d)),g=f(c.async,this,[b].concat(d)),e()&&j(g);return this}function f(a,b,c){function d(){for(var d,e=0,f=a.length;!d&&f>e;e+=1)d=a[e].apply(b,c)===!1;return!d}return d}function g(){var a;return a=window.setImmediate?function(a){setImmediate(function(){a()})}:function(a){setTimeout(function(){a()},0)}}function h(a,b){return a.bind?a.bind(b):function(){a.apply(b,[].slice.call(arguments,0))}}var i=/\s+/,j=g();return{onSync:c,onAsync:b,off:d,trigger:e}}(),g=function(a){"use strict";function c(a,c,d){for(var e,f=[],g=0,h=a.length;h>g;g++)f.push(b.escapeRegExChars(a[g]));return e=d?"\\b("+f.join("|")+")\\b":"("+f.join("|")+")",c?new RegExp(e):new RegExp(e,"i")}var d={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(e){function f(b){var c,d,f;return(c=h.exec(b.data))&&(f=a.createElement(e.tagName),e.className&&(f.className=e.className),d=b.splitText(c.index),d.splitText(c[0].length),f.appendChild(d.cloneNode(!0)),b.parentNode.replaceChild(f,d)),!!c}function g(a,b){for(var c,d=3,e=0;e<a.childNodes.length;e++)c=a.childNodes[e],c.nodeType===d?e+=b(c)?1:0:g(c,b)}var h;e=b.mixin({},d,e),e.node&&e.pattern&&(e.pattern=b.isArray(e.pattern)?e.pattern:[e.pattern],h=c(e.pattern,e.caseSensitive,e.wordsOnly),g(e.node,f))}}(window.document),h=function(){"use strict";function c(c){var e,f,g,i,j=this;c=c||{},c.input||a.error("input is missing"),e=b.bind(this._onBlur,this),f=b.bind(this._onFocus,this),g=b.bind(this._onKeydown,this),i=b.bind(this._onInput,this),this.$hint=a(c.hint),this.$input=a(c.input).on("blur.tt",e).on("focus.tt",f).on("keydown.tt",g),0===this.$hint.length&&(this.setHint=this.getHint=this.clearHint=this.clearHintIfInvalid=b.noop),b.isMsie()?this.$input.on("keydown.tt keypress.tt cut.tt paste.tt",function(a){h[a.which||a.keyCode]||b.defer(b.bind(j._onInput,j,a))}):this.$input.on("input.tt",i),this.query=this.$input.val(),this.$overflowHelper=d(this.$input)}function d(b){return a('<pre aria-hidden="true"></pre>').css({position:"absolute",visibility:"hidden",whiteSpace:"pre",fontFamily:b.css("font-family"),fontSize:b.css("font-size"),fontStyle:b.css("font-style"),fontVariant:b.css("font-variant"),fontWeight:b.css("font-weight"),wordSpacing:b.css("word-spacing"),letterSpacing:b.css("letter-spacing"),textIndent:b.css("text-indent"),textRendering:b.css("text-rendering"),textTransform:b.css("text-transform")}).insertAfter(b)}function e(a,b){return c.normalizeQuery(a)===c.normalizeQuery(b)}function g(a){return a.altKey||a.ctrlKey||a.metaKey||a.shiftKey}var h;return h={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},c.normalizeQuery=function(a){return(a||"").replace(/^\s*/g,"").replace(/\s{2,}/g," ")},b.mixin(c.prototype,f,{_onBlur:function(){this.resetInputValue(),this.trigger("blurred")},_onFocus:function(){this.trigger("focused")},_onKeydown:function(a){var b=h[a.which||a.keyCode];this._managePreventDefault(b,a),b&&this._shouldTrigger(b,a)&&this.trigger(b+"Keyed",a)},_onInput:function(){this._checkInputValue()},_managePreventDefault:function(a,b){var c,d,e;switch(a){case"tab":d=this.getHint(),e=this.getInputValue(),c=d&&d!==e&&!g(b);break;case"up":case"down":c=!g(b);break;default:c=!1}c&&b.preventDefault()},_shouldTrigger:function(a,b){var c;switch(a){case"tab":c=!g(b);break;default:c=!0}return c},_checkInputValue:function(){var a,b,c;a=this.getInputValue(),b=e(a,this.query),c=b?this.query.length!==a.length:!1,this.query=a,b?c&&this.trigger("whitespaceChanged",this.query):this.trigger("queryChanged",this.query)},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(a){this.query=a},getInputValue:function(){return this.$input.val()},setInputValue:function(a,b){this.$input.val(a),b?this.clearHint():this._checkInputValue()},resetInputValue:function(){this.setInputValue(this.query,!0)},getHint:function(){return this.$hint.val()},setHint:function(a){this.$hint.val(a)},clearHint:function(){this.setHint("")},clearHintIfInvalid:function(){var a,b,c,d;a=this.getInputValue(),b=this.getHint(),c=a!==b&&0===b.indexOf(a),d=""!==a&&c&&!this.hasOverflow(),!d&&this.clearHint()},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},hasOverflow:function(){var a=this.$input.width()-2;return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>=a},isCursorAtEnd:function(){var a,c,d;return a=this.$input.val().length,c=this.$input[0].selectionStart,b.isNumber(c)?c===a:document.selection?(d=document.selection.createRange(),d.moveStart("character",-a),a===d.text.length):!0},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null}}),c}(),i=function(){"use strict";function e(d){d=d||{},d.templates=d.templates||{},d.source||a.error("missing source"),d.name&&!j(d.name)&&a.error("invalid dataset name: "+d.name),this.query=null,this.highlight=!!d.highlight,this.name=d.name||b.getUniqueId(),this.source=d.source,this.displayFn=h(d.display||d.displayKey),this.templates=i(d.templates,this.displayFn),this.$el=a(c.dataset.replace("%CLASS%",this.name))}function h(a){function c(b){return b[a]}return a=a||"value",b.isFunction(a)?a:c}function i(a,c){function d(a){return"<p>"+c(a)+"</p>"}return{empty:a.empty&&b.templatify(a.empty),header:a.header&&b.templatify(a.header),footer:a.footer&&b.templatify(a.footer),suggestion:a.suggestion||d}}function j(a){return/^[_a-zA-Z0-9-]+$/.test(a)}var k="ttDataset",l="ttValue",m="ttDatum";return e.extractDatasetName=function(b){return a(b).data(k)},e.extractValue=function(b){return a(b).data(l)},e.extractDatum=function(b){return a(b).data(m)},b.mixin(e.prototype,f,{_render:function(e,f){function h(){return p.templates.empty({query:e,isEmpty:!0})}function i(){function h(b){var e;return e=a(c.suggestion).append(p.templates.suggestion(b)).data(k,p.name).data(l,p.displayFn(b)).data(m,b),e.children().each(function(){a(this).css(d.suggestionChild)}),e}var i,j;return i=a(c.suggestions).css(d.suggestions),j=b.map(f,h),i.append.apply(i,j),p.highlight&&g({className:"tt-highlight",node:i[0],pattern:e}),i}function j(){return p.templates.header({query:e,isEmpty:!o})}function n(){return p.templates.footer({query:e,isEmpty:!o})}if(this.$el){var o,p=this;this.$el.empty(),o=f&&f.length,!o&&this.templates.empty?this.$el.html(h()).prepend(p.templates.header?j():null).append(p.templates.footer?n():null):o&&this.$el.html(i()).prepend(p.templates.header?j():null).append(p.templates.footer?n():null),this.trigger("rendered")}},getRoot:function(){return this.$el},update:function(a){function b(b){c.canceled||a!==c.query||c._render(a,b)}var c=this;this.query=a,this.canceled=!1,this.source(a,b)},cancel:function(){this.canceled=!0},clear:function(){this.cancel(),this.$el.empty(),this.trigger("rendered")},isEmpty:function(){return this.$el.is(":empty")},destroy:function(){this.$el=null}}),e}(),j=function(){"use strict";function c(c){var d,f,g,h=this;c=c||{},c.menu||a.error("menu is required"),this.isOpen=!1,this.isEmpty=!0,this.datasets=b.map(c.datasets,e),d=b.bind(this._onSuggestionClick,this),f=b.bind(this._onSuggestionMouseEnter,this),g=b.bind(this._onSuggestionMouseLeave,this),this.$menu=a(c.menu).on("click.tt",".tt-suggestion",d).on("mouseenter.tt",".tt-suggestion",f).on("mouseleave.tt",".tt-suggestion",g),b.each(this.datasets,function(a){h.$menu.append(a.getRoot()),a.onSync("rendered",h._onRendered,h)})}function e(a){return new i(a)}return b.mixin(c.prototype,f,{_onSuggestionClick:function(b){this.trigger("suggestionClicked",a(b.currentTarget))},_onSuggestionMouseEnter:function(b){this._removeCursor(),this._setCursor(a(b.currentTarget),!0)},_onSuggestionMouseLeave:function(){this._removeCursor()},_onRendered:function(){function a(a){return a.isEmpty()}this.isEmpty=b.every(this.datasets,a),this.isEmpty?this._hide():this.isOpen&&this._show(),this.trigger("datasetRendered")},_hide:function(){this.$menu.hide()},_show:function(){this.$menu.css("display","block")},_getSuggestions:function(){return this.$menu.find(".tt-suggestion")},_getCursor:function(){return this.$menu.find(".tt-cursor").first()},_setCursor:function(a,b){a.first().addClass("tt-cursor"),!b&&this.trigger("cursorMoved")},_removeCursor:function(){this._getCursor().removeClass("tt-cursor")},_moveCursor:function(a){var b,c,d,e;if(this.isOpen){if(c=this._getCursor(),b=this._getSuggestions(),this._removeCursor(),d=b.index(c)+a,d=(d+1)%(b.length+1)-1,-1===d)return void this.trigger("cursorRemoved");-1>d&&(d=b.length-1),this._setCursor(e=b.eq(d)),this._ensureVisible(e)}},_ensureVisible:function(a){var b,c,d,e;b=a.position().top,c=b+a.outerHeight(!0),d=this.$menu.scrollTop(),e=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),0>b?this.$menu.scrollTop(d+b):c>e&&this.$menu.scrollTop(d+(c-e))},close:function(){this.isOpen&&(this.isOpen=!1,this._removeCursor(),this._hide(),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(a){this.$menu.css("ltr"===a?d.ltr:d.rtl)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getDatumForSuggestion:function(a){var b=null;return a.length&&(b={raw:i.extractDatum(a),value:i.extractValue(a),datasetName:i.extractDatasetName(a)}),b},getDatumForCursor:function(){return this.getDatumForSuggestion(this._getCursor().first())},getDatumForTopSuggestion:function(){return this.getDatumForSuggestion(this._getSuggestions().first())},update:function(a){function c(b){b.update(a)}b.each(this.datasets,c)},empty:function(){function a(a){a.clear()}b.each(this.datasets,a),this.isEmpty=!0},isVisible:function(){return this.isOpen&&!this.isEmpty},destroy:function(){function a(a){a.destroy()}this.$menu.off(".tt"),this.$menu=null,b.each(this.datasets,a)}}),c}(),k=function(){"use strict";function f(c){var d,f,i;c=c||{},c.input||a.error("missing input"),this.isActivated=!1,this.autoselect=!!c.autoselect,this.minLength=b.isNumber(c.minLength)?c.minLength:1,this.$node=g(c.input,c.withHint),d=this.$node.find(".tt-dropdown-menu"),f=this.$node.find(".tt-input"),i=this.$node.find(".tt-hint"),f.on("blur.tt",function(a){var c,e,g;c=document.activeElement,e=d.is(c),g=d.has(c).length>0,b.isMsie()&&(e||g)&&(a.preventDefault(),a.stopImmediatePropagation(),b.defer(function(){f.focus()}))}),d.on("mousedown.tt",function(a){a.preventDefault()}),this.eventBus=c.eventBus||new e({el:f}),this.dropdown=new j({menu:d,datasets:c.datasets}).onSync("suggestionClicked",this._onSuggestionClicked,this).onSync("cursorMoved",this._onCursorMoved,this).onSync("cursorRemoved",this._onCursorRemoved,this).onSync("opened",this._onOpened,this).onSync("closed",this._onClosed,this).onAsync("datasetRendered",this._onDatasetRendered,this),this.input=new h({input:f,hint:i}).onSync("focused",this._onFocused,this).onSync("blurred",this._onBlurred,this).onSync("enterKeyed",this._onEnterKeyed,this).onSync("tabKeyed",this._onTabKeyed,this).onSync("escKeyed",this._onEscKeyed,this).onSync("upKeyed",this._onUpKeyed,this).onSync("downKeyed",this._onDownKeyed,this).onSync("leftKeyed",this._onLeftKeyed,this).onSync("rightKeyed",this._onRightKeyed,this).onSync("queryChanged",this._onQueryChanged,this).onSync("whitespaceChanged",this._onWhitespaceChanged,this),this._setLanguageDirection()}function g(b,e){var f,g,h,j;f=a(b),g=a(c.wrapper).css(d.wrapper),h=a(c.dropdown).css(d.dropdown),j=f.clone().css(d.hint).css(i(f)),j.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder required").prop("readonly",!0).attr({autocomplete:"off",spellcheck:"false",tabindex:-1}),f.data(l,{dir:f.attr("dir"),autocomplete:f.attr("autocomplete"),spellcheck:f.attr("spellcheck"),style:f.attr("style")}),f.addClass("tt-input").attr({autocomplete:"off",spellcheck:!1}).css(e?d.input:d.inputWithNoHint);try{!f.attr("dir")&&f.attr("dir","auto")}catch(k){}return f.wrap(g).parent().prepend(e?j:null).append(h)}function i(a){return{backgroundAttachment:a.css("background-attachment"),backgroundClip:a.css("background-clip"),backgroundColor:a.css("background-color"),backgroundImage:a.css("background-image"),backgroundOrigin:a.css("background-origin"),backgroundPosition:a.css("background-position"),backgroundRepeat:a.css("background-repeat"),backgroundSize:a.css("background-size")}}function k(a){var c=a.find(".tt-input");b.each(c.data(l),function(a,d){b.isUndefined(a)?c.removeAttr(d):c.attr(d,a)}),c.detach().removeData(l).removeClass("tt-input").insertAfter(a),a.remove()}var l="ttAttrs";return b.mixin(f.prototype,{_onSuggestionClicked:function(a,b){var c;(c=this.dropdown.getDatumForSuggestion(b))&&this._select(c)},_onCursorMoved:function(){var a=this.dropdown.getDatumForCursor();this.input.setInputValue(a.value,!0),this.eventBus.trigger("cursorchanged",a.raw,a.datasetName)},_onCursorRemoved:function(){this.input.resetInputValue(),this._updateHint()},_onDatasetRendered:function(){this._updateHint()},_onOpened:function(){this._updateHint(),this.eventBus.trigger("opened")},_onClosed:function(){this.input.clearHint(),this.eventBus.trigger("closed")},_onFocused:function(){this.isActivated=!0,this.dropdown.open()},_onBlurred:function(){this.isActivated=!1,this.dropdown.empty(),this.dropdown.close()},_onEnterKeyed:function(a,b){var c,d;c=this.dropdown.getDatumForCursor(),d=this.dropdown.getDatumForTopSuggestion(),c?(this._select(c),b.preventDefault()):this.autoselect&&d&&(this._select(d),b.preventDefault())},_onTabKeyed:function(a,b){var c;(c=this.dropdown.getDatumForCursor())?(this._select(c),b.preventDefault()):this._autocomplete(!0)},_onEscKeyed:function(){this.dropdown.close(),this.input.resetInputValue()},_onUpKeyed:function(){var a=this.input.getQuery();this.dropdown.isEmpty&&a.length>=this.minLength?this.dropdown.update(a):this.dropdown.moveCursorUp(),this.dropdown.open()},_onDownKeyed:function(){var a=this.input.getQuery();this.dropdown.isEmpty&&a.length>=this.minLength?this.dropdown.update(a):this.dropdown.moveCursorDown(),this.dropdown.open()},_onLeftKeyed:function(){"rtl"===this.dir&&this._autocomplete()},_onRightKeyed:function(){"ltr"===this.dir&&this._autocomplete()},_onQueryChanged:function(a,b){this.input.clearHintIfInvalid(),b.length>=this.minLength?this.dropdown.update(b):this.dropdown.empty(),this.dropdown.open(),this._setLanguageDirection()},_onWhitespaceChanged:function(){this._updateHint(),this.dropdown.open()},_setLanguageDirection:function(){var a;this.dir!==(a=this.input.getLanguageDirection())&&(this.dir=a,this.$node.css("direction",a),this.dropdown.setLanguageDirection(a))},_updateHint:function(){var a,c,d,e,f,g;a=this.dropdown.getDatumForTopSuggestion(),a&&this.dropdown.isVisible()&&!this.input.hasOverflow()?(c=this.input.getInputValue(),d=h.normalizeQuery(c),e=b.escapeRegExChars(d),f=new RegExp("^(?:"+e+")(.+$)","i"),g=f.exec(a.value),g?this.input.setHint(c+g[1]):this.input.clearHint()):this.input.clearHint()},_autocomplete:function(a){var b,c,d,e;b=this.input.getHint(),c=this.input.getQuery(),d=a||this.input.isCursorAtEnd(),b&&c!==b&&d&&(e=this.dropdown.getDatumForTopSuggestion(),e&&this.input.setInputValue(e.value),this.eventBus.trigger("autocompleted",e.raw,e.datasetName))},_select:function(a){this.input.setQuery(a.value),this.input.setInputValue(a.value,!0),this._setLanguageDirection(),this.eventBus.trigger("selected",a.raw,a.datasetName),this.dropdown.close(),b.defer(b.bind(this.dropdown.empty,this.dropdown))},open:function(){this.dropdown.open()},close:function(){this.dropdown.close()},setVal:function(a){a=b.toStr(a),this.isActivated?this.input.setInputValue(a):(this.input.setQuery(a),this.input.setInputValue(a,!0)),this._setLanguageDirection()},getVal:function(){return this.input.getQuery()},destroy:function(){this.input.destroy(),this.dropdown.destroy(),k(this.$node),this.$node=null}}),f}();!function(){"use strict";var c,d,f;c=a.fn.typeahead,d="ttTypeahead",f={initialize:function(c,f){function g(){var g,h,i=a(this);b.each(f,function(a){a.highlight=!!c.highlight}),h=new k({input:i,eventBus:g=new e({el:i}),withHint:b.isUndefined(c.hint)?!0:!!c.hint,minLength:c.minLength,autoselect:c.autoselect,datasets:f}),i.data(d,h)}return f=b.isArray(f)?f:[].slice.call(arguments,1),c=c||{},this.each(g)},open:function(){function b(){var b,c=a(this);(b=c.data(d))&&b.open()}return this.each(b)},close:function(){function b(){var b,c=a(this);(b=c.data(d))&&b.close()}return this.each(b)},val:function(b){function c(){var c,e=a(this);(c=e.data(d))&&c.setVal(b)}function e(a){var b,c;return(b=a.data(d))&&(c=b.getVal()),c}return arguments.length?this.each(c):e(this.first())},destroy:function(){function b(){var b,c=a(this);(b=c.data(d))&&(b.destroy(),c.removeData(d))}return this.each(b)}},a.fn.typeahead=function(b){var c;return f[b]&&"initialize"!==b?(c=this.filter(function(){return!!a(this).data(d)}),f[b].apply(c,[].slice.call(arguments,1))):f.initialize.apply(this,arguments)},a.fn.typeahead.noConflict=function(){return a.fn.typeahead=c,this}}()}(window.jQuery);
@@ -0,0 +1,2667 @@
1
+ /*
2
+ * Copyright (c) 2013 Algolia
3
+ * http://www.algolia.com/
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in
13
+ * all copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ * THE SOFTWARE.
22
+ */
23
+
24
+ var ALGOLIA_VERSION = '2.9.4';
25
+
26
+ /*
27
+ * Copyright (c) 2013 Algolia
28
+ * http://www.algolia.com/
29
+ *
30
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
31
+ * of this software and associated documentation files (the "Software"), to deal
32
+ * in the Software without restriction, including without limitation the rights
33
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
+ * copies of the Software, and to permit persons to whom the Software is
35
+ * furnished to do so, subject to the following conditions:
36
+ *
37
+ * The above copyright notice and this permission notice shall be included in
38
+ * all copies or substantial portions of the Software.
39
+ *
40
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46
+ * THE SOFTWARE.
47
+ */
48
+
49
+ /*
50
+ * Algolia Search library initialization
51
+ * @param applicationID the application ID you have in your admin interface
52
+ * @param apiKey a valid API key for the service
53
+ * @param methodOrOptions the hash of parameters for initialization. It can contains:
54
+ * - method (optional) specify if the protocol used is http or https (http by default to make the first search query faster).
55
+ * You need to use https is you are doing something else than just search queries.
56
+ * - hosts (optional) the list of hosts that you have received for the service
57
+ * - dsn (optional) set to true if your account has the Distributed Search Option
58
+ * - dsnHost (optional) override the automatic computation of dsn hostname
59
+ */
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
+ }
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);
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
+ };
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
+
152
+ function AlgoliaExplainResults(hit, titleAttribute, otherAttributes) {
153
+
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]') {
195
+ var res = [];
196
+ for (var j = 0; j < obj.length; ++j) {
197
+ res = res.concat(_getHitExplanationForOneAttr(obj[j], foundWords, array.slice(i).join('.')));
198
+ }
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
+ }
222
+ }
223
+ }
224
+ return res;
225
+ }
226
+
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;
287
+ }
288
+
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
+ },
400
+
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]);
417
+ }
418
+ }
419
+ tags = strTags.join(',');
420
+ }
421
+ this.tagFilters = tags;
422
+ },
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
+ },
431
+
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
+ },
492
+
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
+ },
504
+
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;
514
+
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
+ },
527
+
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
+ }
544
+ }
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
+ }
581
+
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);
590
+ }
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);
603
+ }
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();
615
+ } else {
616
+ opts.successiveRetryCount = 0;
617
+ deferred && (success ? deferred.resolve(body) : deferred.reject(body));
618
+ if (!self._isUndefined(callback) && callback) {
619
+ callback(success, body);
620
+ }
621
+ }
622
+ };
623
+ opts.hostname = self.hosts[self.currentHostIndex];
624
+ self._jsonRequestByHost(opts);
625
+ };
626
+ impl();
627
+
628
+ return deferred && deferred.promise;
629
+ },
630
+
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
+ },
645
+
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;
655
+
656
+ if (!this._isUndefined(opts.body)) {
657
+ body = JSON.stringify(opts.body);
658
+ }
659
+
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
+ },
691
+
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;
701
+
702
+ if (!this._isUndefined(opts.body)) {
703
+ body = JSON.stringify(opts.body);
704
+ }
705
+
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 );
727
+ } else {
728
+ opts.callback(true, false, { 'message': error } );
729
+ }
730
+ },
731
+ success: function(data, textStatus, xhr) {
732
+ opts.callback(false, true, data);
733
+ }
734
+ });
735
+ },
736
+
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
+ }
748
+
749
+ var cbCalled = false;
750
+ var timedOut = false;
751
+
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;
760
+
761
+ window[cb] = function(data) {
762
+ try { delete window[cb]; } catch (e) { window[cb] = undefined; }
763
+
764
+ if (timedOut) {
765
+ return;
766
+ }
767
+
768
+ var status =
769
+ data && data.message && data.status ||
770
+ data && 200;
771
+
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
+ };
777
+
778
+ script.type = 'text/javascript';
779
+ url += '?callback=' + cb + '&X-Algolia-Application-Id=' + this.applicationID + '&X-Algolia-API-Key=' + this.apiKey;
780
+
781
+ if (this.tagFilters) {
782
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
783
+ }
784
+
785
+ if (this.userToken) {
786
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
787
+ }
788
+
789
+ for (var i = 0; i < this.extraHeaders.length; ++i) {
790
+ url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
791
+ }
792
+
793
+ if (opts.body && opts.body.params) {
794
+ url += '&' + opts.body.params;
795
+ }
796
+
797
+ ontimeout = setTimeout(function() {
798
+ timedOut = true;
799
+ clean();
800
+
801
+ opts.callback(true, false, { 'message': 'Timeout - Failed to load JSONP script.' });
802
+ }, this.requestTimeoutInMs);
803
+
804
+ success = function() {
805
+ if (done || timedOut) {
806
+ return;
807
+ }
808
+
809
+ done = true;
810
+ clean();
811
+
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
+ };
817
+
818
+ clean = function() {
819
+ clearTimeout(ontimeout);
820
+ script.onload = null;
821
+ script.onreadystatechange = null;
822
+ script.onerror = null;
823
+ head.removeChild(script);
824
+
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
+ };
833
+
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
+ };
842
+
843
+ script.onload = function() {
844
+ success();
845
+ };
846
+
847
+ script.onerror = function() {
848
+ if (done || timedOut) {
849
+ return;
850
+ }
851
+
852
+ clean();
853
+ opts.callback(true, false, { 'message': 'Failed to load JSONP script.' });
854
+ };
855
+
856
+ script.async = true;
857
+ script.defer = true;
858
+ script.src = url;
859
+
860
+ head.appendChild(script);
861
+ },
862
+
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
+ }
876
+
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;
883
+
884
+ if (!this._isUndefined(opts.body)) {
885
+ body = JSON.stringify(opts.body);
886
+ }
887
+
888
+ url += (url.indexOf('?') === -1 ? '?' : '&') + 'X-Algolia-API-Key=' + this.apiKey;
889
+ url += '&X-Algolia-Application-Id=' + this.applicationID;
890
+
891
+ if (this.userToken) {
892
+ url += '&X-Algolia-UserToken=' + encodeURIComponent(this.userToken);
893
+ }
894
+
895
+ if (this.tagFilters) {
896
+ url += '&X-Algolia-TagFilters=' + encodeURIComponent(this.tagFilters);
897
+ }
898
+
899
+ for (var i = 0; i < this.extraHeaders.length; ++i) {
900
+ url += '&' + this.extraHeaders[i].key + '=' + this.extraHeaders[i].value;
901
+ }
902
+
903
+ timeoutListener = function() {
904
+ if (!self._support.timeout) {
905
+ timedOut = true;
906
+ request.abort();
907
+ }
908
+
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) {}
942
+
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
+ }
1014
+ };
1015
+
1016
+ /*
1017
+ * Contains all the functions related to one index
1018
+ * You should use AlgoliaSearch.initIndex(indexName) to retrieve this object
1019
+ */
1020
+ AlgoliaSearch.prototype.Index.prototype = {
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
+ }
1050
+
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
+ }
1098
+
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
+ }
1270
+
1271
+ // no query = getAllObjects
1272
+ if (typeof query === 'function') {
1273
+ callback = query;
1274
+ query = '';
1275
+ }
1276
+
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
+ },
1580
+
1581
+ // internal attributes
1582
+ as: null,
1583
+ indexName: null,
1584
+ typeAheadArgs: null,
1585
+ typeAheadValueOption: null
1586
+ };
1587
+
1588
+ /*
1589
+ * Copyright (c) 2014 Algolia
1590
+ * http://www.algolia.com/
1591
+ *
1592
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
1593
+ * of this software and associated documentation files (the "Software"), to deal
1594
+ * in the Software without restriction, including without limitation the rights
1595
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
1596
+ * copies of the Software, and to permit persons to whom the Software is
1597
+ * furnished to do so, subject to the following conditions:
1598
+ *
1599
+ * The above copyright notice and this permission notice shall be included in
1600
+ * all copies or substantial portions of the Software.
1601
+ *
1602
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1603
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1604
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1605
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1606
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1607
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1608
+ * THE SOFTWARE.
1609
+ */
1610
+
1611
+ (function($) {
1612
+ var extend = function(out) {
1613
+ out = out || {};
1614
+ for (var i = 1; i < arguments.length; i++) {
1615
+ if (!arguments[i]) {
1616
+ continue;
1617
+ }
1618
+ for (var key in arguments[i]) {
1619
+ if (arguments[i].hasOwnProperty(key)) {
1620
+ out[key] = arguments[i][key];
1621
+ }
1622
+ }
1623
+ }
1624
+ return out;
1625
+ };
1626
+
1627
+ /**
1628
+ * Algolia Search Helper providing faceting and disjunctive faceting
1629
+ * @param {AlgoliaSearch} client an AlgoliaSearch client
1630
+ * @param {string} index the index name to query
1631
+ * @param {hash} options an associative array defining the hitsPerPage, list of facets, the list of disjunctive facets and the default facet filters
1632
+ */
1633
+ window.AlgoliaSearchHelper = function(client, index, options) {
1634
+ /// Default options
1635
+ var defaults = {
1636
+ facets: [], // list of facets to compute
1637
+ disjunctiveFacets: [], // list of disjunctive facets to compute
1638
+ hitsPerPage: 20, // number of hits per page
1639
+ defaultFacetFilters: [] // the default list of facetFilters
1640
+ };
1641
+
1642
+ this.init(client, index, extend({}, defaults, options));
1643
+ };
1644
+
1645
+ AlgoliaSearchHelper.prototype = {
1646
+ /**
1647
+ * Initialize a new AlgoliaSearchHelper
1648
+ * @param {AlgoliaSearch} client an AlgoliaSearch client
1649
+ * @param {string} index the index name to query
1650
+ * @param {hash} options an associative array defining the hitsPerPage, list of facets and list of disjunctive facets
1651
+ * @return {AlgoliaSearchHelper}
1652
+ */
1653
+ init: function(client, index, options) {
1654
+ this.client = client;
1655
+ this.index = index;
1656
+ this.options = options;
1657
+ this.page = 0;
1658
+ this.refinements = {};
1659
+ this.excludes = {};
1660
+ this.disjunctiveRefinements = {};
1661
+ this.extraQueries = [];
1662
+ },
1663
+
1664
+ /**
1665
+ * Perform a query
1666
+ * @param {string} q the user query
1667
+ * @param {function} searchCallback the result callback called with two arguments:
1668
+ * success: boolean set to true if the request was successfull
1669
+ * content: the query answer with an extra 'disjunctiveFacets' attribute
1670
+ */
1671
+ search: function(q, searchCallback, searchParams) {
1672
+ this.q = q;
1673
+ this.searchCallback = searchCallback;
1674
+ this.searchParams = searchParams || {};
1675
+ this.page = this.page || 0;
1676
+ this.refinements = this.refinements || {};
1677
+ this.disjunctiveRefinements = this.disjunctiveRefinements || {};
1678
+ this._search();
1679
+ },
1680
+
1681
+ /**
1682
+ * Remove all refinements (disjunctive + conjunctive)
1683
+ */
1684
+ clearRefinements: function() {
1685
+ this.disjunctiveRefinements = {};
1686
+ this.refinements = {};
1687
+ },
1688
+
1689
+ /**
1690
+ * Ensure a facet refinement exists
1691
+ * @param {string} facet the facet to refine
1692
+ * @param {string} value the associated value
1693
+ */
1694
+ addDisjunctiveRefine: function(facet, value) {
1695
+ this.disjunctiveRefinements = this.disjunctiveRefinements || {};
1696
+ this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {};
1697
+ this.disjunctiveRefinements[facet][value] = true;
1698
+ },
1699
+
1700
+ /**
1701
+ * Ensure a facet refinement does not exist
1702
+ * @param {string} facet the facet to refine
1703
+ * @param {string} value the associated value
1704
+ */
1705
+ removeDisjunctiveRefine: function(facet, value) {
1706
+ this.disjunctiveRefinements = this.disjunctiveRefinements || {};
1707
+ this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {};
1708
+ try {
1709
+ delete this.disjunctiveRefinements[facet][value];
1710
+ } catch (e) {
1711
+ this.disjunctiveRefinements[facet][value] = undefined; // IE compat
1712
+ }
1713
+ },
1714
+
1715
+ /**
1716
+ * Ensure a facet refinement exists
1717
+ * @param {string} facet the facet to refine
1718
+ * @param {string} value the associated value
1719
+ */
1720
+ addRefine: function(facet, value) {
1721
+ var refinement = facet + ':' + value;
1722
+ this.refinements = this.refinements || {};
1723
+ this.refinements[refinement] = true;
1724
+ },
1725
+
1726
+ /**
1727
+ * Ensure a facet refinement does not exist
1728
+ * @param {string} facet the facet to refine
1729
+ * @param {string} value the associated value
1730
+ */
1731
+ removeRefine: function(facet, value) {
1732
+ var refinement = facet + ':' + value;
1733
+ this.refinements = this.refinements || {};
1734
+ this.refinements[refinement] = false;
1735
+ },
1736
+
1737
+ /**
1738
+ * Ensure a facet exclude exists
1739
+ * @param {string} facet the facet to refine
1740
+ * @param {string} value the associated value
1741
+ */
1742
+ addExclude: function(facet, value) {
1743
+ var refinement = facet + ':-' + value;
1744
+ this.excludes = this.excludes || {};
1745
+ this.excludes[refinement] = true;
1746
+ },
1747
+
1748
+ /**
1749
+ * Ensure a facet exclude does not exist
1750
+ * @param {string} facet the facet to refine
1751
+ * @param {string} value the associated value
1752
+ */
1753
+ removeExclude: function(facet, value) {
1754
+ var refinement = facet + ':-' + value;
1755
+ this.excludes = this.excludes || {};
1756
+ this.excludes[refinement] = false;
1757
+ },
1758
+
1759
+ /**
1760
+ * Toggle refinement state of an exclude
1761
+ * @param {string} facet the facet to refine
1762
+ * @param {string} value the associated value
1763
+ * @return {boolean} true if the facet has been found
1764
+ */
1765
+ toggleExclude: function(facet, value) {
1766
+ for (var i = 0; i < this.options.facets.length; ++i) {
1767
+ if (this.options.facets[i] == facet) {
1768
+ var refinement = facet + ':-' + value;
1769
+ this.excludes[refinement] = !this.excludes[refinement];
1770
+ this.page = 0;
1771
+ this._search();
1772
+ return true;
1773
+ }
1774
+ }
1775
+ return false;
1776
+ },
1777
+
1778
+ /**
1779
+ * Toggle refinement state of a facet
1780
+ * @param {string} facet the facet to refine
1781
+ * @param {string} value the associated value
1782
+ * @return {boolean} true if the facet has been found
1783
+ */
1784
+ toggleRefine: function(facet, value) {
1785
+ for (var i = 0; i < this.options.facets.length; ++i) {
1786
+ if (this.options.facets[i] == facet) {
1787
+ var refinement = facet + ':' + value;
1788
+ this.refinements[refinement] = !this.refinements[refinement];
1789
+ this.page = 0;
1790
+ this._search();
1791
+ return true;
1792
+ }
1793
+ }
1794
+ this.disjunctiveRefinements[facet] = this.disjunctiveRefinements[facet] || {};
1795
+ for (var j = 0; j < this.options.disjunctiveFacets.length; ++j) {
1796
+ if (this.options.disjunctiveFacets[j] == facet) {
1797
+ this.disjunctiveRefinements[facet][value] = !this.disjunctiveRefinements[facet][value];
1798
+ this.page = 0;
1799
+ this._search();
1800
+ return true;
1801
+ }
1802
+ }
1803
+ return false;
1804
+ },
1805
+
1806
+ /**
1807
+ * Check the refinement state of a facet
1808
+ * @param {string} facet the facet
1809
+ * @param {string} value the associated value
1810
+ * @return {boolean} true if refined
1811
+ */
1812
+ isRefined: function(facet, value) {
1813
+ var refinement = facet + ':' + value;
1814
+ if (this.refinements[refinement]) {
1815
+ return true;
1816
+ }
1817
+ if (this.disjunctiveRefinements[facet] && this.disjunctiveRefinements[facet][value]) {
1818
+ return true;
1819
+ }
1820
+ return false;
1821
+ },
1822
+
1823
+ /**
1824
+ * Check the exclude state of a facet
1825
+ * @param {string} facet the facet
1826
+ * @param {string} value the associated value
1827
+ * @return {boolean} true if refined
1828
+ */
1829
+ isExcluded: function(facet, value) {
1830
+ var refinement = facet + ':-' + value;
1831
+ if (this.excludes[refinement]) {
1832
+ return true;
1833
+ }
1834
+ return false;
1835
+ },
1836
+
1837
+ /**
1838
+ * Go to next page
1839
+ */
1840
+ nextPage: function() {
1841
+ this._gotoPage(this.page + 1);
1842
+ },
1843
+
1844
+ /**
1845
+ * Go to previous page
1846
+ */
1847
+ previousPage: function() {
1848
+ if (this.page > 0) {
1849
+ this._gotoPage(this.page - 1);
1850
+ }
1851
+ },
1852
+
1853
+ /**
1854
+ * Goto a page
1855
+ * @param {integer} page The page number
1856
+ */
1857
+ gotoPage: function(page) {
1858
+ this._gotoPage(page);
1859
+ },
1860
+
1861
+ /**
1862
+ * Configure the page but do not trigger a reload
1863
+ * @param {integer} page The page number
1864
+ */
1865
+ setPage: function(page) {
1866
+ this.page = page;
1867
+ },
1868
+
1869
+ /**
1870
+ * Configure the underlying index name
1871
+ * @param {string} name the index name
1872
+ */
1873
+ setIndex: function(name) {
1874
+ this.index = name;
1875
+ },
1876
+
1877
+ /**
1878
+ * Get the underlying configured index name
1879
+ */
1880
+ getIndex: function() {
1881
+ return this.index;
1882
+ },
1883
+
1884
+ /**
1885
+ * Clear the extra queries added to the underlying batch of queries
1886
+ */
1887
+ clearExtraQueries: function() {
1888
+ this.extraQueries = [];
1889
+ },
1890
+
1891
+ /**
1892
+ * Add an extra query to the underlying batch of queries. Once you add queries
1893
+ * to the batch, the 2nd parameter of the searchCallback will be an object with a `results`
1894
+ * attribute listing all search results.
1895
+ */
1896
+ addExtraQuery: function(index, query, params) {
1897
+ this.extraQueries.push({ index: index, query: query, params: (params || {}) });
1898
+ },
1899
+
1900
+ ///////////// PRIVATE
1901
+
1902
+ /**
1903
+ * Goto a page
1904
+ * @param {integer} page The page number
1905
+ */
1906
+ _gotoPage: function(page) {
1907
+ this.page = page;
1908
+ this._search();
1909
+ },
1910
+
1911
+ /**
1912
+ * Perform the underlying queries
1913
+ */
1914
+ _search: function() {
1915
+ this.client.startQueriesBatch();
1916
+ this.client.addQueryInBatch(this.index, this.q, this._getHitsSearchParams());
1917
+ var disjunctiveFacets = [];
1918
+ var unusedDisjunctiveFacets = {};
1919
+ var i = 0;
1920
+ for (i = 0; i < this.options.disjunctiveFacets.length; ++i) {
1921
+ var facet = this.options.disjunctiveFacets[i];
1922
+ if (this._hasDisjunctiveRefinements(facet)) {
1923
+ disjunctiveFacets.push(facet);
1924
+ } else {
1925
+ unusedDisjunctiveFacets[facet] = true;
1926
+ }
1927
+ }
1928
+ for (i = 0; i < disjunctiveFacets.length; ++i) {
1929
+ this.client.addQueryInBatch(this.index, this.q, this._getDisjunctiveFacetSearchParams(disjunctiveFacets[i]));
1930
+ }
1931
+ for (i = 0; i < this.extraQueries.length; ++i) {
1932
+ this.client.addQueryInBatch(this.extraQueries[i].index, this.extraQueries[i].query, this.extraQueries[i].params);
1933
+ }
1934
+ var self = this;
1935
+ this.client.sendQueriesBatch(function(success, content) {
1936
+ if (!success) {
1937
+ self.searchCallback(false, content);
1938
+ return;
1939
+ }
1940
+ var aggregatedAnswer = content.results[0];
1941
+ aggregatedAnswer.disjunctiveFacets = aggregatedAnswer.disjunctiveFacets || {};
1942
+ aggregatedAnswer.facets_stats = aggregatedAnswer.facets_stats || {};
1943
+ // create disjunctive facets from facets (disjunctive facets without refinements)
1944
+ for (var facet in unusedDisjunctiveFacets) {
1945
+ if (aggregatedAnswer.facets[facet] && !aggregatedAnswer.disjunctiveFacets[facet]) {
1946
+ aggregatedAnswer.disjunctiveFacets[facet] = aggregatedAnswer.facets[facet];
1947
+ try {
1948
+ delete aggregatedAnswer.facets[facet];
1949
+ } catch (e) {
1950
+ aggregatedAnswer.facets[facet] = undefined; // IE compat
1951
+ }
1952
+ }
1953
+ }
1954
+ // aggregate the disjunctive facets
1955
+ for (i = 0; i < disjunctiveFacets.length; ++i) {
1956
+ for (var dfacet in content.results[i + 1].facets) {
1957
+ aggregatedAnswer.disjunctiveFacets[dfacet] = content.results[i + 1].facets[dfacet];
1958
+ if (self.disjunctiveRefinements[dfacet]) {
1959
+ for (var value in self.disjunctiveRefinements[dfacet]) {
1960
+ // add the disjunctive reginements if it is no more retrieved
1961
+ if (!aggregatedAnswer.disjunctiveFacets[dfacet][value] && self.disjunctiveRefinements[dfacet][value]) {
1962
+ aggregatedAnswer.disjunctiveFacets[dfacet][value] = 0;
1963
+ }
1964
+ }
1965
+ }
1966
+ }
1967
+ // aggregate the disjunctive facets stats
1968
+ for (var stats in content.results[i + 1].facets_stats) {
1969
+ aggregatedAnswer.facets_stats[stats] = content.results[i + 1].facets_stats[stats];
1970
+ }
1971
+ }
1972
+
1973
+ // Backward compatibility
1974
+ aggregatedAnswer.facetStats = aggregatedAnswer.facets_stats;
1975
+
1976
+ // add the excludes
1977
+ for (var exclude in self.excludes) {
1978
+ if (self.excludes[exclude]) {
1979
+ var e = exclude.indexOf(':-');
1980
+ var facet = exclude.slice(0, e);
1981
+ var value = exclude.slice(e + 2);
1982
+ aggregatedAnswer.facets[facet] = aggregatedAnswer.facets[facet] || {};
1983
+ if (!aggregatedAnswer.facets[facet][value]) {
1984
+ aggregatedAnswer.facets[facet][value] = 0;
1985
+ }
1986
+ }
1987
+ }
1988
+ // call the actual callback
1989
+ if (self.extraQueries.length === 0) {
1990
+ self.searchCallback(true, aggregatedAnswer);
1991
+ } else {
1992
+ // append the extra queries
1993
+ var c = { results: [ aggregatedAnswer ] };
1994
+ for (i = 0; i < self.extraQueries.length; ++i) {
1995
+ c.results.push(content.results[1 + disjunctiveFacets.length + i]);
1996
+ }
1997
+ self.searchCallback(true, c);
1998
+ }
1999
+ });
2000
+ },
2001
+
2002
+ /**
2003
+ * Build search parameters used to fetch hits
2004
+ * @return {hash}
2005
+ */
2006
+ _getHitsSearchParams: function() {
2007
+ var facets = [];
2008
+ var i = 0;
2009
+ for (i = 0; i < this.options.facets.length; ++i) {
2010
+ facets.push(this.options.facets[i]);
2011
+ }
2012
+ for (i = 0; i < this.options.disjunctiveFacets.length; ++i) {
2013
+ var facet = this.options.disjunctiveFacets[i];
2014
+ if (!this._hasDisjunctiveRefinements(facet)) {
2015
+ facets.push(facet);
2016
+ }
2017
+ }
2018
+ return extend({}, {
2019
+ hitsPerPage: this.options.hitsPerPage,
2020
+ page: this.page,
2021
+ facets: facets,
2022
+ facetFilters: this._getFacetFilters()
2023
+ }, this.searchParams);
2024
+ },
2025
+
2026
+ /**
2027
+ * Build search parameters used to fetch a disjunctive facet
2028
+ * @param {string} facet the associated facet name
2029
+ * @return {hash}
2030
+ */
2031
+ _getDisjunctiveFacetSearchParams: function(facet) {
2032
+ return extend({}, this.searchParams, {
2033
+ hitsPerPage: 1,
2034
+ page: 0,
2035
+ attributesToRetrieve: [],
2036
+ attributesToHighlight: [],
2037
+ attributesToSnippet: [],
2038
+ facets: facet,
2039
+ facetFilters: this._getFacetFilters(facet),
2040
+ analytics: false
2041
+ });
2042
+ },
2043
+
2044
+ /**
2045
+ * Test if there are some disjunctive refinements on the facet
2046
+ */
2047
+ _hasDisjunctiveRefinements: function(facet) {
2048
+ for (var value in this.disjunctiveRefinements[facet]) {
2049
+ if (this.disjunctiveRefinements[facet][value]) {
2050
+ return true;
2051
+ }
2052
+ }
2053
+ return false;
2054
+ },
2055
+
2056
+ /**
2057
+ * Build facetFilters parameter based on current refinements
2058
+ * @param {string} facet if set, the current disjunctive facet
2059
+ * @return {hash}
2060
+ */
2061
+ _getFacetFilters: function(facet) {
2062
+ var facetFilters = [];
2063
+ if (this.options.defaultFacetFilters) {
2064
+ for (var i = 0; i < this.options.defaultFacetFilters.length; ++i) {
2065
+ facetFilters.push(this.options.defaultFacetFilters[i]);
2066
+ }
2067
+ }
2068
+ for (var refinement in this.refinements) {
2069
+ if (this.refinements[refinement]) {
2070
+ facetFilters.push(refinement);
2071
+ }
2072
+ }
2073
+ for (var refinement in this.excludes) {
2074
+ if (this.excludes[refinement]) {
2075
+ facetFilters.push(refinement);
2076
+ }
2077
+ }
2078
+ for (var disjunctiveRefinement in this.disjunctiveRefinements) {
2079
+ if (disjunctiveRefinement != facet) {
2080
+ var refinements = [];
2081
+ for (var value in this.disjunctiveRefinements[disjunctiveRefinement]) {
2082
+ if (this.disjunctiveRefinements[disjunctiveRefinement][value]) {
2083
+ refinements.push(disjunctiveRefinement + ':' + value);
2084
+ }
2085
+ }
2086
+ if (refinements.length > 0) {
2087
+ facetFilters.push(refinements);
2088
+ }
2089
+ }
2090
+ }
2091
+ return facetFilters;
2092
+ }
2093
+ };
2094
+ })();
2095
+
2096
+ /*
2097
+ * Copyright (c) 2014 Algolia
2098
+ * http://www.algolia.com/
2099
+ *
2100
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
2101
+ * of this software and associated documentation files (the "Software"), to deal
2102
+ * in the Software without restriction, including without limitation the rights
2103
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2104
+ * copies of the Software, and to permit persons to whom the Software is
2105
+ * furnished to do so, subject to the following conditions:
2106
+ *
2107
+ * The above copyright notice and this permission notice shall be included in
2108
+ * all copies or substantial portions of the Software.
2109
+ *
2110
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2111
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2112
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2113
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2114
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2115
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2116
+ * THE SOFTWARE.
2117
+ */
2118
+
2119
+ (function($) {
2120
+
2121
+ /**
2122
+ * Algolia Places API
2123
+ * @param {string} Your application ID
2124
+ * @param {string} Your API Key
2125
+ */
2126
+ window.AlgoliaPlaces = function(applicationID, apiKey) {
2127
+ this.init(applicationID, apiKey);
2128
+ };
2129
+
2130
+ AlgoliaPlaces.prototype = {
2131
+ /**
2132
+ * @param {string} Your application ID
2133
+ * @param {string} Your API Key
2134
+ */
2135
+ init: function(applicationID, apiKey) {
2136
+ this.client = new AlgoliaSearch(applicationID, apiKey, 'http', true, ['places-1.algolia.io', 'places-2.algolia.io', 'places-3.algolia.io']);
2137
+ this.cache = {};
2138
+ },
2139
+
2140
+ /**
2141
+ * Perform a query
2142
+ * @param {string} q the user query
2143
+ * @param {function} searchCallback the result callback called with two arguments:
2144
+ * success: boolean set to true if the request was successfull
2145
+ * content: the query answer with an extra 'disjunctiveFacets' attribute
2146
+ * @param {hash} the list of search parameters
2147
+ */
2148
+ search: function(q, searchCallback, searchParams) {
2149
+ var indexObj = this;
2150
+ var params = 'query=' + encodeURIComponent(q);
2151
+ if (!this.client._isUndefined(searchParams) && searchParams != null) {
2152
+ params = this.client._getSearchParams(searchParams, params);
2153
+ }
2154
+ var pObj = {params: params, apiKey: this.client.apiKey, appID: this.client.applicationID};
2155
+ this.client._jsonRequest({ cache: this.cache,
2156
+ method: 'POST',
2157
+ url: '/1/places/query',
2158
+ body: pObj,
2159
+ callback: searchCallback,
2160
+ removeCustomHTTPHeaders: true });
2161
+ }
2162
+ };
2163
+ })();
2164
+
2165
+ /*
2166
+ json2.js
2167
+ 2014-02-04
2168
+
2169
+ Public Domain.
2170
+
2171
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
2172
+
2173
+ See http://www.JSON.org/js.html
2174
+
2175
+
2176
+ This code should be minified before deployment.
2177
+ See http://javascript.crockford.com/jsmin.html
2178
+
2179
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
2180
+ NOT CONTROL.
2181
+
2182
+
2183
+ This file creates a global JSON object containing two methods: stringify
2184
+ and parse.
2185
+
2186
+ JSON.stringify(value, replacer, space)
2187
+ value any JavaScript value, usually an object or array.
2188
+
2189
+ replacer an optional parameter that determines how object
2190
+ values are stringified for objects. It can be a
2191
+ function or an array of strings.
2192
+
2193
+ space an optional parameter that specifies the indentation
2194
+ of nested structures. If it is omitted, the text will
2195
+ be packed without extra whitespace. If it is a number,
2196
+ it will specify the number of spaces to indent at each
2197
+ level. If it is a string (such as '\t' or '&nbsp;'),
2198
+ it contains the characters used to indent at each level.
2199
+
2200
+ This method produces a JSON text from a JavaScript value.
2201
+
2202
+ When an object value is found, if the object contains a toJSON
2203
+ method, its toJSON method will be called and the result will be
2204
+ stringified. A toJSON method does not serialize: it returns the
2205
+ value represented by the name/value pair that should be serialized,
2206
+ or undefined if nothing should be serialized. The toJSON method
2207
+ will be passed the key associated with the value, and this will be
2208
+ bound to the value
2209
+
2210
+ For example, this would serialize Dates as ISO strings.
2211
+
2212
+ Date.prototype.toJSON = function (key) {
2213
+ function f(n) {
2214
+ // Format integers to have at least two digits.
2215
+ return n < 10 ? '0' + n : n;
2216
+ }
2217
+
2218
+ return this.getUTCFullYear() + '-' +
2219
+ f(this.getUTCMonth() + 1) + '-' +
2220
+ f(this.getUTCDate()) + 'T' +
2221
+ f(this.getUTCHours()) + ':' +
2222
+ f(this.getUTCMinutes()) + ':' +
2223
+ f(this.getUTCSeconds()) + 'Z';
2224
+ };
2225
+
2226
+ You can provide an optional replacer method. It will be passed the
2227
+ key and value of each member, with this bound to the containing
2228
+ object. The value that is returned from your method will be
2229
+ serialized. If your method returns undefined, then the member will
2230
+ be excluded from the serialization.
2231
+
2232
+ If the replacer parameter is an array of strings, then it will be
2233
+ used to select the members to be serialized. It filters the results
2234
+ such that only members with keys listed in the replacer array are
2235
+ stringified.
2236
+
2237
+ Values that do not have JSON representations, such as undefined or
2238
+ functions, will not be serialized. Such values in objects will be
2239
+ dropped; in arrays they will be replaced with null. You can use
2240
+ a replacer function to replace those with JSON values.
2241
+ JSON.stringify(undefined) returns undefined.
2242
+
2243
+ The optional space parameter produces a stringification of the
2244
+ value that is filled with line breaks and indentation to make it
2245
+ easier to read.
2246
+
2247
+ If the space parameter is a non-empty string, then that string will
2248
+ be used for indentation. If the space parameter is a number, then
2249
+ the indentation will be that many spaces.
2250
+
2251
+ Example:
2252
+
2253
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
2254
+ // text is '["e",{"pluribus":"unum"}]'
2255
+
2256
+
2257
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
2258
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
2259
+
2260
+ text = JSON.stringify([new Date()], function (key, value) {
2261
+ return this[key] instanceof Date ?
2262
+ 'Date(' + this[key] + ')' : value;
2263
+ });
2264
+ // text is '["Date(---current time---)"]'
2265
+
2266
+
2267
+ JSON.parse(text, reviver)
2268
+ This method parses a JSON text to produce an object or array.
2269
+ It can throw a SyntaxError exception.
2270
+
2271
+ The optional reviver parameter is a function that can filter and
2272
+ transform the results. It receives each of the keys and values,
2273
+ and its return value is used instead of the original value.
2274
+ If it returns what it received, then the structure is not modified.
2275
+ If it returns undefined then the member is deleted.
2276
+
2277
+ Example:
2278
+
2279
+ // Parse the text. Values that look like ISO date strings will
2280
+ // be converted to Date objects.
2281
+
2282
+ myData = JSON.parse(text, function (key, value) {
2283
+ var a;
2284
+ if (typeof value === 'string') {
2285
+ a =
2286
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
2287
+ if (a) {
2288
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
2289
+ +a[5], +a[6]));
2290
+ }
2291
+ }
2292
+ return value;
2293
+ });
2294
+
2295
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
2296
+ var d;
2297
+ if (typeof value === 'string' &&
2298
+ value.slice(0, 5) === 'Date(' &&
2299
+ value.slice(-1) === ')') {
2300
+ d = new Date(value.slice(5, -1));
2301
+ if (d) {
2302
+ return d;
2303
+ }
2304
+ }
2305
+ return value;
2306
+ });
2307
+
2308
+
2309
+ This is a reference implementation. You are free to copy, modify, or
2310
+ redistribute.
2311
+ */
2312
+
2313
+ /*jslint evil: true, regexp: true */
2314
+
2315
+ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
2316
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
2317
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
2318
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
2319
+ test, toJSON, toString, valueOf
2320
+ */
2321
+
2322
+
2323
+ // Create a JSON object only if one does not already exist. We create the
2324
+ // methods in a closure to avoid creating global variables.
2325
+
2326
+ if (typeof JSON !== 'object') {
2327
+ JSON = {};
2328
+ }
2329
+
2330
+ (function () {
2331
+ 'use strict';
2332
+
2333
+ function f(n) {
2334
+ // Format integers to have at least two digits.
2335
+ return n < 10 ? '0' + n : n;
2336
+ }
2337
+
2338
+ if (typeof Date.prototype.toJSON !== 'function') {
2339
+
2340
+ Date.prototype.toJSON = function () {
2341
+
2342
+ return isFinite(this.valueOf())
2343
+ ? this.getUTCFullYear() + '-' +
2344
+ f(this.getUTCMonth() + 1) + '-' +
2345
+ f(this.getUTCDate()) + 'T' +
2346
+ f(this.getUTCHours()) + ':' +
2347
+ f(this.getUTCMinutes()) + ':' +
2348
+ f(this.getUTCSeconds()) + 'Z'
2349
+ : null;
2350
+ };
2351
+
2352
+ String.prototype.toJSON =
2353
+ Number.prototype.toJSON =
2354
+ Boolean.prototype.toJSON = function () {
2355
+ return this.valueOf();
2356
+ };
2357
+ }
2358
+
2359
+ var cx,
2360
+ escapable,
2361
+ gap,
2362
+ indent,
2363
+ meta,
2364
+ rep;
2365
+
2366
+
2367
+ function quote(string) {
2368
+
2369
+ // If the string contains no control characters, no quote characters, and no
2370
+ // backslash characters, then we can safely slap some quotes around it.
2371
+ // Otherwise we must also replace the offending characters with safe escape
2372
+ // sequences.
2373
+
2374
+ escapable.lastIndex = 0;
2375
+ return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
2376
+ var c = meta[a];
2377
+ return typeof c === 'string'
2378
+ ? c
2379
+ : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
2380
+ }) + '"' : '"' + string + '"';
2381
+ }
2382
+
2383
+
2384
+ function str(key, holder) {
2385
+
2386
+ // Produce a string from holder[key].
2387
+
2388
+ var i, // The loop counter.
2389
+ k, // The member key.
2390
+ v, // The member value.
2391
+ length,
2392
+ mind = gap,
2393
+ partial,
2394
+ value = holder[key];
2395
+
2396
+ // If the value has a toJSON method, call it to obtain a replacement value.
2397
+
2398
+ if (value && typeof value === 'object' &&
2399
+ typeof value.toJSON === 'function') {
2400
+ value = value.toJSON(key);
2401
+ }
2402
+
2403
+ // If we were called with a replacer function, then call the replacer to
2404
+ // obtain a replacement value.
2405
+
2406
+ if (typeof rep === 'function') {
2407
+ value = rep.call(holder, key, value);
2408
+ }
2409
+
2410
+ // What happens next depends on the value's type.
2411
+
2412
+ switch (typeof value) {
2413
+ case 'string':
2414
+ return quote(value);
2415
+
2416
+ case 'number':
2417
+
2418
+ // JSON numbers must be finite. Encode non-finite numbers as null.
2419
+
2420
+ return isFinite(value) ? String(value) : 'null';
2421
+
2422
+ case 'boolean':
2423
+ case 'null':
2424
+
2425
+ // If the value is a boolean or null, convert it to a string. Note:
2426
+ // typeof null does not produce 'null'. The case is included here in
2427
+ // the remote chance that this gets fixed someday.
2428
+
2429
+ return String(value);
2430
+
2431
+ // If the type is 'object', we might be dealing with an object or an array or
2432
+ // null.
2433
+
2434
+ case 'object':
2435
+
2436
+ // Due to a specification blunder in ECMAScript, typeof null is 'object',
2437
+ // so watch out for that case.
2438
+
2439
+ if (!value) {
2440
+ return 'null';
2441
+ }
2442
+
2443
+ // Make an array to hold the partial results of stringifying this object value.
2444
+
2445
+ gap += indent;
2446
+ partial = [];
2447
+
2448
+ // Is the value an array?
2449
+
2450
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
2451
+
2452
+ // The value is an array. Stringify every element. Use null as a placeholder
2453
+ // for non-JSON values.
2454
+
2455
+ length = value.length;
2456
+ for (i = 0; i < length; i += 1) {
2457
+ partial[i] = str(i, value) || 'null';
2458
+ }
2459
+
2460
+ // Join all of the elements together, separated with commas, and wrap them in
2461
+ // brackets.
2462
+
2463
+ v = partial.length === 0
2464
+ ? '[]'
2465
+ : gap
2466
+ ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
2467
+ : '[' + partial.join(',') + ']';
2468
+ gap = mind;
2469
+ return v;
2470
+ }
2471
+
2472
+ // If the replacer is an array, use it to select the members to be stringified.
2473
+
2474
+ if (rep && typeof rep === 'object') {
2475
+ length = rep.length;
2476
+ for (i = 0; i < length; i += 1) {
2477
+ if (typeof rep[i] === 'string') {
2478
+ k = rep[i];
2479
+ v = str(k, value);
2480
+ if (v) {
2481
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
2482
+ }
2483
+ }
2484
+ }
2485
+ } else {
2486
+
2487
+ // Otherwise, iterate through all of the keys in the object.
2488
+
2489
+ for (k in value) {
2490
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
2491
+ v = str(k, value);
2492
+ if (v) {
2493
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
2494
+ }
2495
+ }
2496
+ }
2497
+ }
2498
+
2499
+ // Join all of the member texts together, separated with commas,
2500
+ // and wrap them in braces.
2501
+
2502
+ v = partial.length === 0
2503
+ ? '{}'
2504
+ : gap
2505
+ ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
2506
+ : '{' + partial.join(',') + '}';
2507
+ gap = mind;
2508
+ return v;
2509
+ }
2510
+ }
2511
+
2512
+ // If the JSON object does not yet have a stringify method, give it one.
2513
+
2514
+ if (typeof JSON.stringify !== 'function') {
2515
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
2516
+ meta = { // table of character substitutions
2517
+ '\b': '\\b',
2518
+ '\t': '\\t',
2519
+ '\n': '\\n',
2520
+ '\f': '\\f',
2521
+ '\r': '\\r',
2522
+ '"' : '\\"',
2523
+ '\\': '\\\\'
2524
+ };
2525
+ JSON.stringify = function (value, replacer, space) {
2526
+
2527
+ // The stringify method takes a value and an optional replacer, and an optional
2528
+ // space parameter, and returns a JSON text. The replacer can be a function
2529
+ // that can replace values, or an array of strings that will select the keys.
2530
+ // A default replacer method can be provided. Use of the space parameter can
2531
+ // produce text that is more easily readable.
2532
+
2533
+ var i;
2534
+ gap = '';
2535
+ indent = '';
2536
+
2537
+ // If the space parameter is a number, make an indent string containing that
2538
+ // many spaces.
2539
+
2540
+ if (typeof space === 'number') {
2541
+ for (i = 0; i < space; i += 1) {
2542
+ indent += ' ';
2543
+ }
2544
+
2545
+ // If the space parameter is a string, it will be used as the indent string.
2546
+
2547
+ } else if (typeof space === 'string') {
2548
+ indent = space;
2549
+ }
2550
+
2551
+ // If there is a replacer, it must be a function or an array.
2552
+ // Otherwise, throw an error.
2553
+
2554
+ rep = replacer;
2555
+ if (replacer && typeof replacer !== 'function' &&
2556
+ (typeof replacer !== 'object' ||
2557
+ typeof replacer.length !== 'number')) {
2558
+ throw new Error('JSON.stringify');
2559
+ }
2560
+
2561
+ // Make a fake root object containing our value under the key of ''.
2562
+ // Return the result of stringifying the value.
2563
+
2564
+ return str('', {'': value});
2565
+ };
2566
+ }
2567
+
2568
+
2569
+ // If the JSON object does not yet have a parse method, give it one.
2570
+
2571
+ if (typeof JSON.parse !== 'function') {
2572
+ cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
2573
+ JSON.parse = function (text, reviver) {
2574
+
2575
+ // The parse method takes a text and an optional reviver function, and returns
2576
+ // a JavaScript value if the text is a valid JSON text.
2577
+
2578
+ var j;
2579
+
2580
+ function walk(holder, key) {
2581
+
2582
+ // The walk method is used to recursively walk the resulting structure so
2583
+ // that modifications can be made.
2584
+
2585
+ var k, v, value = holder[key];
2586
+ if (value && typeof value === 'object') {
2587
+ for (k in value) {
2588
+ if (Object.prototype.hasOwnProperty.call(value, k)) {
2589
+ v = walk(value, k);
2590
+ if (v !== undefined) {
2591
+ value[k] = v;
2592
+ } else {
2593
+ delete value[k];
2594
+ }
2595
+ }
2596
+ }
2597
+ }
2598
+ return reviver.call(holder, key, value);
2599
+ }
2600
+
2601
+
2602
+ // Parsing happens in four stages. In the first stage, we replace certain
2603
+ // Unicode characters with escape sequences. JavaScript handles many characters
2604
+ // incorrectly, either silently deleting them, or treating them as line endings.
2605
+
2606
+ text = String(text);
2607
+ cx.lastIndex = 0;
2608
+ if (cx.test(text)) {
2609
+ text = text.replace(cx, function (a) {
2610
+ return '\\u' +
2611
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
2612
+ });
2613
+ }
2614
+
2615
+ // In the second stage, we run the text against regular expressions that look
2616
+ // for non-JSON patterns. We are especially concerned with '()' and 'new'
2617
+ // because they can cause invocation, and '=' because it can cause mutation.
2618
+ // But just to be safe, we want to reject all unexpected forms.
2619
+
2620
+ // We split the second stage into 4 regexp operations in order to work around
2621
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
2622
+ // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
2623
+ // replace all simple value tokens with ']' characters. Third, we delete all
2624
+ // open brackets that follow a colon or comma or that begin the text. Finally,
2625
+ // we look to see that the remaining characters are only whitespace or ']' or
2626
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
2627
+
2628
+ if (/^[\],:{}\s]*$/
2629
+ .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
2630
+ .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
2631
+ .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
2632
+
2633
+ // In the third stage we use the eval function to compile the text into a
2634
+ // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
2635
+ // in JavaScript: it can begin a block or an object literal. We wrap the text
2636
+ // in parens to eliminate the ambiguity.
2637
+
2638
+ j = eval('(' + text + ')');
2639
+
2640
+ // In the optional fourth stage, we recursively walk the new structure, passing
2641
+ // each name/value pair to a reviver function for possible transformation.
2642
+
2643
+ return typeof reviver === 'function'
2644
+ ? walk({'': j}, '')
2645
+ : j;
2646
+ }
2647
+
2648
+ // If the text is not JSON parseable, then a SyntaxError is thrown.
2649
+
2650
+ throw new SyntaxError('JSON.parse');
2651
+ };
2652
+ }
2653
+ }());
2654
+
2655
+ /* global angular */
2656
+ angular.module('algoliasearch', [])
2657
+ .service('algolia', ['$injector', function ($injector) {
2658
+ return {
2659
+ Client: function(applicationID, apiKey, options) {
2660
+ options = options || {};
2661
+ options.angular = {
2662
+ '$injector': $injector
2663
+ };
2664
+ return new AlgoliaSearch(applicationID, apiKey, options);
2665
+ }
2666
+ };
2667
+ }]);