instantsearch.js
instantsearch.js is a library of widgets to build high performance instant search experiences using Algolia
API is unstable. We welcome any idea and pull request.
Setup
npm, browserify, webpack
npm install instantsearch.js --save-dev
<script>
instantsearch.js is available on jsDelivr:
<script src="//cdn.jsdelivr.net/instantsearch.js/0/instantsearch.min.js"></script>
Usage
var instantsearch = require('instantsearch.js');
var search = instantsearch({
appId: appId,
apiKey: apiKey,
indexName: indexName,
numberLocale: 'fr-FR'
urlSync: {
useHash: false
}
});
search.addWidget(
instantsearch.widgets.searchBox({
container: '#search-box',
placeholder: 'Search for libraries in France...'
})
);
search.start();
Widget API
function mySuperWidget(opts) {
return {
getConfiguration: function(searchParameters) {
return {
}
},
init: function(initialState, helper) {
},
render: function(results, state, helper) {
}
}
}
search.addWidget(mySuperWidget());
Templates
Most of the widgets accept a template
or templates
option that let you
change the default rendering.
template
can be defined either as a Mustache (Hogan) string or as a function receiving
the widget data.
See the documentation of each widget to see which data is passed to the
template.
Examples
search.addWidget(
instantsearch.widgets.stats({
container: '#stats',
templates: {
body: '<div>You have {{nbHits}} results, fetched in {{processingTimeMS}}ms.</div>'
}
})
);
search.addWidget(
instantsearch.widgets.stats({
container: '#stats',
templates: {
body: function(data) {
return '<div>You have ' + data.nbHits + 'results, fetched in ' + data.processingTimMS +'ms.</div>'
}
}
})
);
Template configuration
In order to help you when defining your templates, instantsearch.js
exposes
a few helpers. All helpers are accessible in the Mustache templating through
{{#helpers.nameOfTheHelper}}{{valueToFormat}}{{/helpers.nameOfTheHelper}}
. To
use them in the function templates, you'll have to call
search.templatesConfig.helpers.nameOfTheHelper
where search
is your current
instantsearch
instance.
Here is the list of the currently available helpers:
formatNumber
: Will accept a number as input and returned the formatted
version of the number in the locale defined with the numberLocale
config
option (defaults to en-EN
).
eg. 100000
will be formatted as 100 000
with en-EN
Here is the syntax of a helper (render
is using search.templatesConfig.compileOptions
):
search.templatesConfig.helpers.emphasis = function(text, render) {
return '<em>' + render(text) + '</em>';
};
In your helper, this
always refers to the data:
search.templatesConfig.helpers.discount = function() {
var discount = this.price * 0.3;
return '$ -' + discount;
};
You can configure the options passed to Hogan.compile
by using search.templatesConfig.compileOptions
. We accept all compile options.
Theses options will be passed to the Hogan.compile
calls when you pass a custom template.
Themes
To help get you started, we provide a default theme for the widgets. This is
just a css
file that you have to add to your page to add basic styling.
It is available from jsDelivr:
<link rel="stylesheet" href="//cdn.jsdelivr.net/instantsearch.js/0/themes/default.min.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/instantsearch.js/0/themes/default.css">
It contains (empty) selectors for all the possible markup added by the widgets,
so you can use it as a base for creating your own custom theme. We will provide
more themes in the future.
Development workflow
npm run dev
Test
npm test
npm run test:watch
npm run test:watch:browser
npm run test:watch:browser -- --browsers ChromeCanary
Instant search configuration
The main configuration of instantsearch.js is done through a configuration object.
The minimal configuration is made a of three attributes :
instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name'
});
It can also contain other optionnal attributes to enable other features.
Number locale
For the display of numbers, the locale will be determined by
the browsers or forced in the configuration :
instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name',
numberLocale: 'en-US'
});
Initial search parameters
At the start of instantsearch, the search configuration is based on the input
of each widget and the URL. It is also possible to change the defaults of
the configuration through an object that can contain any parameters understood
by the Algolia API.
instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name',
searchParameters: {
typoTolerance: 'strict'
}
});
URL synchronisation
Instantsearch let you synchronize the url with the current search parameters.
In order to activate this feature, you need to add the urlSync object. It accepts
3 parameters :
- trackedParameters:string[] parameters that will be synchronized in the
URL. By default, it will track the query, all the refinable attribute (facets and numeric
filters), the index and the page.
- useHash:boolean if set to true, the url will be hash based. Otherwise,
it'll use the query parameters using the modern history API.
- threshold:number time in ms after which a new state is created in the browser
history. The default value is 700.
All those parameters are optional and a minimal configuration looks like :
instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name',
urlSync: {}
});
Available widgets
searchBox
API
Usage
<input id="search-box" />
Or
<div id="search-box"></div>
search.addWidget(
instantsearch.widgets.searchBox({
container: '#search-box',
placeholder: 'Search for products',
cssClass: 'form-control',
poweredBy: true
})
);
Styling
<input class="ais-search-box--input">
<div class="ais-search-box--powered-by">
Powered by
<a class="ais-search-box--powered-by-link">Algolia</a>
</div>
.ais-search-box--input {
}
.ais-search-box--powered-by {
}
.ais-search-box--powered-by-link {
}
stats
API
Usage
<div id="stats"></div>
search.addWidget(
instantsearch.widgets.stats({
container: '#stats',
cssClasses: {
time: 'label label-info'
}
})
);
Styling
<div class="ais-stats">
<div class="ais-stats--header ais-header">[custom header template]</div>
<div class="ais-stats--body">
42 results found in <span class="ais-stats--time">42ms</span>
</div>
<div class="ais-stats--footer ais-footer">[custom footer template]</div>
</div>
.ais-stats {
}
.ais-stats--header {
}
.ais-stats--body {
}
.ais-stats--time {
font-size: small;
}
.ais-stats--footer {
}
indexSelector
This widget will let you change the current index being targeted. This is
especially useful for changing the current sort order. If you need your results
ordered following a special rule (like price ascending or price descending),
you'll need several indices. This widget lets you easily change it.
API
Usage
<div id="index-selector"></div>
search.addWidget(
instantsearch.widgets.indexSelector({
container: '#index-selector',
indices: [
{name: 'instant_search', label: 'Most relevant'},
{name: 'instant_search_price_asc', label: 'Lowest price'},
{name: 'instant_search_price_desc', label: 'Highest price'}
],
cssClasses: {
root: 'form-control'
}
})
);
Styling
<select class="ais-index-selector">
<option class="ais-index-selector--item">Most relevant</option>
<option class="ais-index-selector--item">Lowest price</option>
<option class="ais-index-selector--item">Highest price</option>
</select>
.ais-index-selector {
}
.ais-index-selector--item {
}
hitsPerPageSelector
This widget will let you change the current number of results being
displayed per page.
API
Usage
<div id="hits-per-page-selector"></div>
search.addWidget(
instantsearch.widgets.hitsPerPageSelector({
container: '#hits-per-page-selector',
options: [
{value: 6, label: '6 per page'},
{value: 12, label: '12 per page'},
{value: 24, label: '24 per page'}
],
cssClasses: {
select: 'form-control'
}
})
);
Styling
<select class="ais-hits-per-page-selector">
<option class="ais-hits-per-page-selector--item">6 per page</option>
<option class="ais-hits-per-page-selector--item">12 per page</option>
<option class="ais-hits-per-page-selector--item">24 per page</option>
</select>
.ais-hits-per-page-selector {
}
.ais-hits-per-page-selector--item {
}
API
Usage
<div id="pagination"></div>
search.addWidget(
instantsearch.widgets.pagination({
container: '#pagination',
cssClass: 'pagination',
labels: {
prev: '< Previous',
next: 'Next >',
first: '<< First',
last: 'Last >>'
},
maxPages: 10,
showFirstLast: true
})
);
hits
API
Usage
<div id="hits"></div>
search.addWidget(
instantsearch.widgets.hits({
container: '#hits',
templates: {
empty: 'No results'
item: '<div><strong>{{name}}</strong> {{price}}</div>'
},
transformData: {
item: function(data) {
data.price = data.price + '$';
return data;
}
},
hitsPerPage: 20
})
);
Styling
<div class="ais-hits">
<div class="ais-hits--item">Hit content</div>
...
<div class="ais-hits--item">Hit content</div>
</div>
<div class="ais-hits ais-hits__empty">
No results
</div>
.ais-hits {
}
.ais-hits--item {
}
.ais-hits__empty {
}
toggle
This widget is used to add filtering of results on a boolean value. Let's say
you want to only display elements that are eligible to free shipping. You'll
just have to instantiate this widget with a facetName
of free_shipping
(with
free_shipping
being a boolean attribute in your records.
When toggling on this widget, only hits with Free Shipping will be displayed.
When switching it off, all items will be displayed.
Note that we are not toggling from true
to false
here, but from true
to
undefined
.
API
Usage
<div id="free-shipping"></div>
search.addWidget(
instantsearch.widgets.toggle({
container: '#free-shipping',
facetName: 'free_shipping',
label: 'Free Shipping',
templates: {
body: '<label><input type="checkbox" {{#isRefined}}checked{{/isRefined}} />{{label}}</label>'
}
})
);
Styling
<div class="ais-toggle">
<div class="ais-toggle--header ais-header">[custom header template]</div>
<div class="ais-toggle--body">
<div class="ais-toggle--list">
<div class="ais-toggle--item">
<label class="ais-toggle--label">
<input type="checkbox" class="ais-toggle--checkbox" value="your_value"> Your value
<span class="ais-toggle--count">42</span>
</label>
</div>
</div>
</div>
<div class="ais-toggle--footer ais-footer">[custom footer template]</div>
</div>
.ais-toggle {
}
.ais-toggle--header {
}
.ais-toggle--body {
}
.ais-toggle--list {
}
.ais-toggle--item {
}
.ais-toggle--item__active {
}
.ais-toggle--label {
}
.ais-toggle--checkbox {
}
.ais-toggle--count {
}
.ais-toggle--footer {
}
refinementList
API
Usage
<div id="brands"></div>
search.addWidget(
instantsearch.widgets.refinementList({
container: '#brands',
facetName: 'brands'
})
);
Styling
<div class="ais-refinement-list">
<div class="ais-refinement-list--header ais-header">[custom header template]</div>
<div class="ais-refinement-list--body">
<div class="ais-refinement-list--list">
<div class="ais-refinement-list--item">
<label class="ais-refinement-list--label">
<input type="checkbox" class="ais-refinement-list--checkbox" value="your_value"> Your value
<span class="ais-refinement-list--count">42</span>
</label>
</div>
<div class="ais-refinement-list--item ais-refinement-list--item__active">
<label class="ais-refinement-list--label">
<input type="checkbox" class="ais-refinement-list--checkbox" value="your_selected_value" checked="checked"> Your selected value
<span class="ais-refinement-list--count">42</span>
</label>
</div>
</div>
</div>
<div class="ais-refinement-list--footer ais-footer">[custom footer template]</div>
</div>
.ais-refinement-list {
}
.ais-refinement-list--header {
}
.ais-refinement-list--body {
}
.ais-refinement-list--list {
}
.ais-refinement-list--item {
}
.ais-refinement-list--item__active {
}
.ais-refinement-list--label {
}
.ais-refinement-list--checkbox {
}
.ais-refinement-list--count {
}
.ais-refinement-list--footer {
}
API
Usage
<div id="categories"></div>
search.addWidget(
instantsearch.widgets.menu({
container: '#categories',
facetName: 'categories'
})
);
Styling
<div class="ais-menu">
<div class="ais-menu--header ais-header">[custom header template]</div>
<div class="ais-menu--body">
<div class="ais-menu--list">
<div class="ais-menu--item">
<a class="ais-menu--link" href="/url">
Your value
<span class="ais-menu--count">42</span>
</a>
</div>
<div class="ais-menu--item ais-menu--item__active">
<a class="ais-menu--link" href="/url">
Your active value
<span class="ais-menu--count">42</span>
</a>
</div>
</div>
</div>
<div class="ais-menu--footer ais-footer">[custom footer template]</div>
</div>
.ais-menu {
}
.ais-menu--header {
}
.ais-menu--body {
}
.ais-menu--list {
}
.ais-menu--item {
}
.ais-menu--item__active {
}
.ais-menu--link {
}
.ais-menu--count {
}
.ais-menu--footer {
}
rangeSlider
API
Usage
<div id="price"></div>
search.addWidget(
instantsearch.widgets.rangeSlider({
container: '#price',
facetName: 'price',
tooltips: {
format: function(formattedValue) {
return '$' + formattedValue;
}
}
})
);
priceRanges
API
Usage
search.addWidget(
instantsearch.widgets.priceRanges({
container: '#price-ranges',
facetName: 'price'
})
);
Styling
API
Algolia requirements
All the attributes
should be added to attributesForFaceting
in your index settings.
Your index's objects must be formatted in a way that is expected by the hierarchicalMenu
widget:
{
"objectID": "123",
"name": "orange",
"categories": {
"lvl0": "fruits",
"lvl1": "fruits > citrus"
}
}
Usage
search.addWidget(
instantsearch.widgets.hierarchicalMenu({
container: '#products',
attributes: ['categories.lvl0', 'categories.lvl1', 'categories.lvl2']
})
);
Styling
<div class="ais-hierarchical-menu">
<div class="ais-hierarchical-menu--header ais-header">[custom header template]</div>
<div class="ais-hierarchical-menu--body">
<div class="ais-hierarchical-menu--list ais-hierarchical-menu--list__lvl0">
<div class="ais-hierarchical-menu--item">
<a class="ais-hierarchical-menu--link" href="/url">
Your value
<span class="ais-hierarchical-menu--count">42</span>
</a>
</div>
<div class="ais-hierarchical-menu--item ais-hierarchical-menu--item__active">
<a class="ais-hierarchical-menu--link" href="/url">
Your active value
<span class="ais-hierarchical-menu--count">42</span>
</a>
<div class="ais-hierarchical-menu--list ais-hierarchical-menu--list__lvl1">
<div class="ais-hierarchical-menu--item">
<a class="ais-hierarchical-menu--link" href="/url">
Your subvalue 1
<span class="ais-hierarchical-menu--count">10</span>
</a>
</div>
<div class="ais-hierarchical-menu--item">
<a class="ais-hierarchical-menu--link" href="/url">
Your subvalue 2
<span class="ais-hierarchical-menu--count">32</span>
</a>
</div>
</div>
</div>
</div>
</div>
<div class="ais-hierarchical-menu--footer ais-footer">[custom footer template]</div>
</div>
.ais-hierarchical-menu {
}
.ais-hierarchical-menu--header {
}
.ais-hierarchical-menu--body {
}
.ais-hierarchical-menu--list {
}
.ais-hierarchical-menu--list__lvl0 {
}
.ais-hierarchical-menu--list__lvl1 {
}
.ais-hierarchical-menu--item {
}
.ais-hierarchical-menu--item__active {
}
.ais-hierarchical-menu--link {
}
.ais-hierarchical-menu--count {
}
.ais-hierarchical-menu--footer {
}
Browser support
We natively support IE10+ and all other modern browsers without any dependency need
on your side.
To get < IE10 support, please insert this code in the <head>
:
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
We use the polyfill.io.