Skip to content

Commit 23dd505

Browse files
Alexandre Stanislawskivvo
Alexandre Stanislawski
authored and
vvo
committed
feat(urlSync): url generation for widget links. Fix #29
You now have access to {{url}} inside refinement widgets templates, this contains the full url to the current refinement. This way you can now have good links instead of href=#. BREAKING CHANGE: urlSync is not a widget anymore. It's now an option of instantsearch(appID, apiKey, opts);. See the README.md for more info.
1 parent d9a795d commit 23dd505

21 files changed

+291
-224
lines changed

README.md

+75-41
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ var search = instantsearch({
6969
appId: appId, // Mandatory
7070
apiKey: apiKey, // Mandatory
7171
indexName: indexName, // Mandatory
72-
numberLocale: 'fr-FR' // Optional, defaults to 'en-EN'
72+
numberLocale: 'fr-FR' // Optional, defaults to 'en-EN',
73+
urlSync: { // optionnal, activate url sync if defined
74+
useHash: false
75+
}
7376
});
7477

7578
// add a widget
@@ -198,6 +201,77 @@ npm run test:watch:browser # chrome
198201
npm run test:watch:browser -- --browsers ChromeCanary # force Chrome Canary
199202
```
200203

204+
## Instant search configuration
205+
206+
The main configuration of instantsearch.js is done through a configuration object.
207+
The minimal configuration is made a of three attributes :
208+
209+
```js
210+
instantsearch({
211+
appId: 'my_application_id',
212+
apiKey: 'my_search_api_key',
213+
indexName: 'my_index_name'
214+
});
215+
```
216+
217+
It can also contain other optionnal attributes to enable other features.
218+
219+
### Number locale
220+
221+
For the display of numbers, the locale will be determined by
222+
the browsers or forced in the configuration :
223+
224+
```js
225+
instantsearch({
226+
appId: 'my_application_id',
227+
apiKey: 'my_search_api_key',
228+
indexName: 'my_index_name',
229+
numberLocale: 'en-US'
230+
});
231+
```
232+
233+
### Initial search parameters
234+
235+
At the start of instantsearch, the search configuration is based on the input
236+
of each widget and the URL. It is also possible to change the defaults of
237+
the configuration through an object that can contain any parameters understood
238+
by the Algolia API.
239+
240+
```js
241+
instantsearch({
242+
appId: 'my_application_id',
243+
apiKey: 'my_search_api_key',
244+
indexName: 'my_index_name',
245+
searchParameters: {
246+
typoTolerance: 'strict'
247+
}
248+
});
249+
```
250+
251+
### URL synchronisation
252+
253+
Instantsearch let you synchronize the url with the current search parameters.
254+
In order to activate this feature, you need to add the urlSync object. It accepts
255+
3 parameters :
256+
- trackedParameters:string[] parameters that will be synchronized in the
257+
URL. By default, it will track the query, all the refinable attribute (facets and numeric
258+
filters), the index and the page.
259+
- useHash:boolean if set to true, the url will be hash based. Otherwise,
260+
it'll use the query parameters using the modern history API.
261+
- threshold:number time in ms after which a new state is created in the browser
262+
history. The default value is 700.
263+
264+
All those parameters are optional and a minimal configuration looks like :
265+
266+
```js
267+
instantsearch({
268+
appId: 'my_application_id',
269+
apiKey: 'my_search_api_key',
270+
indexName: 'my_index_name',
271+
urlSync: {}
272+
});
273+
```
274+
201275
## Available widgets
202276

203277
[searchBox]: ./widgets-screenshots/search-box.png
@@ -210,7 +284,6 @@ npm run test:watch:browser -- --browsers ChromeCanary # force Chrome Canary
210284
[hierarchicalMenu]: ./widgets-screenshots/hierarchicalMenu.png
211285
[menu]: ./widgets-screenshots/menu.png
212286
[rangeSlider]: ./widgets-screenshots/range-slider.png
213-
[urlSync]: ./widgets-screenshots/url-sync.gif
214287

215288
### searchBox
216289

@@ -720,45 +793,6 @@ search.addWidget(
720793
);
721794
```
722795

723-
### urlSync
724-
725-
![Example of urlSync][urlSync]
726-
727-
#### API
728-
729-
```js
730-
/**
731-
* Instanciate a url sync widget. This widget let you synchronize the search
732-
* parameters with the URL. It can operate with legacy API and hash or it can use
733-
* the modern history API. By default, it will use the modern API, but if you are
734-
* looking for compatibility with IE8 and IE9, then you should set 'useHash' to
735-
* true.
736-
* @class
737-
* @param {UrlUtil} urlUtils an object containing the function to read, watch the changes
738-
* and update the URL.
739-
* @param {object} options may contain the following keys :
740-
* - threshold:number time in ms after which a new state is created in the browser
741-
* history. The default value is 700.
742-
* - trackedParameters:string[] parameters that will be synchronized in the
743-
* URL. By default, it will track the query, all the refinable attribute (facets and numeric
744-
* filters), the index and the page.
745-
* - useHash:boolean if set to true, the url will be hash based. Otherwise,
746-
* it'll use the query parameters using the modern history API.
747-
*/
748-
```
749-
750-
#### Usage
751-
752-
```js
753-
search.addWidget(
754-
instantsearch.widgets.urlSync({
755-
/* useHash: true,
756-
threshold: 600,
757-
trackedParameters: ['query', 'page', 'attribute:*'] */
758-
})
759-
);
760-
```
761-
762796
### hierarchicalMenu
763797

764798
![Example of the hierarchicalMenu widget][hierarchicalMenu]

components/Pagination/Pagination.js

+24-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class Pagination extends React.Component {
1818
this.props.setCurrentPage(pageNumber);
1919
}
2020

21-
pageLink({label, ariaLabel, pageNumber, className = null, isDisabled = false, isActive = false}) {
21+
pageLink({label, ariaLabel, pageNumber, className = null, isDisabled = false, isActive = false, createURL}) {
2222
var handleClick = this.handleClick.bind(this, pageNumber);
2323

2424
className = cx(bem('item'), className);
@@ -29,6 +29,7 @@ class Pagination extends React.Component {
2929
className = cx(bem('item-page', 'active'), this.props.cssClasses.active, className);
3030
}
3131

32+
var url = createURL ? createURL(pageNumber) : '#';
3233

3334
return (
3435
<PaginationLink
@@ -37,55 +38,60 @@ class Pagination extends React.Component {
3738
handleClick={handleClick}
3839
key={label}
3940
label={label}
41+
url={url}
4042
/>
4143
);
4244
}
4345

44-
previousPageLink(pager) {
46+
previousPageLink(pager, createURL) {
4547
var className = cx(bem('item-previous'), this.props.cssClasses.previous);
4648
return this.pageLink({
4749
ariaLabel: 'Previous',
4850
className: className,
4951
isDisabled: pager.isFirstPage(),
5052
label: this.props.labels.previous,
51-
pageNumber: pager.currentPage - 1
53+
pageNumber: pager.currentPage - 1,
54+
createURL
5255
});
5356
}
5457

55-
nextPageLink(pager) {
58+
nextPageLink(pager, createURL) {
5659
var className = cx(bem('item-next'), this.props.cssClasses.next);
5760
return this.pageLink({
5861
ariaLabel: 'Next',
5962
className: className,
6063
isDisabled: pager.isLastPage(),
6164
label: this.props.labels.next,
62-
pageNumber: pager.currentPage + 1
65+
pageNumber: pager.currentPage + 1,
66+
createURL
6367
});
6468
}
6569

66-
firstPageLink(pager) {
70+
firstPageLink(pager, createURL) {
6771
var className = cx(bem('item-first'), this.props.cssClasses.first);
6872
return this.pageLink({
6973
ariaLabel: 'First',
7074
className: className,
7175
isDisabled: pager.isFirstPage(),
7276
label: this.props.labels.first,
73-
pageNumber: 0
77+
pageNumber: 0,
78+
createURL
7479
});
7580
}
7681

77-
lastPageLink(pager) {
82+
lastPageLink(pager, createURL) {
7883
var className = cx(bem('item-last'), this.props.cssClasses.last);
7984
return this.pageLink({
8085
ariaLabel: 'Last',
8186
className: className,
8287
isDisabled: pager.isLastPage(),
8388
label: this.props.labels.last,
84-
pageNumber: pager.total - 1
89+
pageNumber: pager.total - 1,
90+
createURL
8591
});
8692
}
8793

88-
pages(pager) {
94+
pages(pager, createURL) {
8995
var pages = [];
9096
var className = cx(bem('item-page'), this.props.cssClasses.item);
9197

@@ -97,7 +103,8 @@ class Pagination extends React.Component {
97103
className: className,
98104
isActive: isActive,
99105
label: pageNumber + 1,
100-
pageNumber: pageNumber
106+
pageNumber: pageNumber,
107+
createURL
101108
}));
102109
});
103110

@@ -112,14 +119,15 @@ class Pagination extends React.Component {
112119
});
113120

114121
var cssClassesList = cx(bem(null), this.props.cssClasses.root);
122+
var createURL = this.props.createURL;
115123

116124
return (
117125
<ul className={cssClassesList}>
118-
{this.props.showFirstLast ? this.firstPageLink(pager) : null}
119-
{this.previousPageLink(pager)}
120-
{this.pages(pager)}
121-
{this.nextPageLink(pager)}
122-
{this.props.showFirstLast ? this.lastPageLink(pager) : null}
126+
{this.props.showFirstLast ? this.firstPageLink(pager, createURL) : null}
127+
{this.previousPageLink(pager, createURL)}
128+
{this.pages(pager, createURL)}
129+
{this.nextPageLink(pager, createURL)}
130+
{this.props.showFirstLast ? this.lastPageLink(pager, createURL) : null}
123131
</ul>
124132
);
125133
}

components/Pagination/PaginationLink.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ class PaginationLink extends React.Component {
77
}
88

99
render() {
10-
var {className, label, ariaLabel, handleClick} = this.props;
10+
var {className, label, ariaLabel, handleClick, url} = this.props;
1111

1212
return (
1313
<li className={className}>
1414
<a
1515
ariaLabel={ariaLabel}
1616
className={className}
1717
dangerouslySetInnerHTML={{__html: label}}
18-
href="#"
18+
href={url}
1919
onClick={handleClick}
2020
></a>
2121
</li>
@@ -33,7 +33,8 @@ PaginationLink.propTypes = {
3333
label: React.PropTypes.oneOfType([
3434
React.PropTypes.string,
3535
React.PropTypes.number
36-
]).isRequired
36+
]).isRequired,
37+
url: React.PropTypes.string
3738
};
3839

3940
module.exports = PaginationLink;

components/RefinementList.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ class RefinementList extends React.Component {
1111

1212
_generateFacetItem(facetValue) {
1313
var hasChildren = facetValue.data && facetValue.data.length > 0;
14+
var subList = hasChildren && <RefinementList {...this.props} facetValues={facetValue.data} />;
15+
var data = facetValue;
1416

15-
var subList = hasChildren ?
16-
<RefinementList {...this.props} facetValues={facetValue.data} /> :
17-
null;
17+
if (this.props.createURL) {
18+
data.url = this.props.createURL(facetValue[this.props.facetNameKey]);
19+
}
1820

1921
var templateData = {...facetValue, cssClasses: this.props.cssClasses};
2022

@@ -78,13 +80,15 @@ class RefinementList extends React.Component {
7880
render() {
7981
return (
8082
<div className={cx(this.props.cssClasses.list)}>
81-
{this.props.facetValues.map(this._generateFacetItem, this)}
83+
{this.props.facetValues.map(this._generateFacetItem, this)}
8284
</div>
8385
);
8486
}
8587
}
8688

8789
RefinementList.propTypes = {
90+
Template: React.PropTypes.func,
91+
createURL: React.PropTypes.func.isRequired,
8892
cssClasses: React.PropTypes.shape({
8993
item: React.PropTypes.oneOfType([
9094
React.PropTypes.string,

0 commit comments

Comments
 (0)