Skip to content

Commit

Permalink
feat(urlSync): url generation for widget links. Fix #29
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Alexandre Stanislawski authored and vvo committed Oct 19, 2015
1 parent d9a795d commit 23dd505
Show file tree
Hide file tree
Showing 21 changed files with 291 additions and 224 deletions.
116 changes: 75 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ var search = instantsearch({
appId: appId, // Mandatory
apiKey: apiKey, // Mandatory
indexName: indexName, // Mandatory
numberLocale: 'fr-FR' // Optional, defaults to 'en-EN'
numberLocale: 'fr-FR' // Optional, defaults to 'en-EN',
urlSync: { // optionnal, activate url sync if defined
useHash: false
}
});

// add a widget
Expand Down Expand Up @@ -198,6 +201,77 @@ npm run test:watch:browser # chrome
npm run test:watch:browser -- --browsers ChromeCanary # force Chrome Canary
```

## Instant search configuration

The main configuration of instantsearch.js is done through a configuration object.
The minimal configuration is made a of three attributes :

```js
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 :

```js
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.

```js
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 :

```js
instantsearch({
appId: 'my_application_id',
apiKey: 'my_search_api_key',
indexName: 'my_index_name',
urlSync: {}
});
```

## Available widgets

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

### searchBox

Expand Down Expand Up @@ -720,45 +793,6 @@ search.addWidget(
);
```

### urlSync

![Example of urlSync][urlSync]

#### API

```js
/**
* Instanciate a url sync widget. This widget let you synchronize the search
* parameters with the URL. It can operate with legacy API and hash or it can use
* the modern history API. By default, it will use the modern API, but if you are
* looking for compatibility with IE8 and IE9, then you should set 'useHash' to
* true.
* @class
* @param {UrlUtil} urlUtils an object containing the function to read, watch the changes
* and update the URL.
* @param {object} options may contain the following keys :
* - threshold:number time in ms after which a new state is created in the browser
* history. The default value is 700.
* - 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.
*/
```

#### Usage

```js
search.addWidget(
instantsearch.widgets.urlSync({
/* useHash: true,
threshold: 600,
trackedParameters: ['query', 'page', 'attribute:*'] */
})
);
```

### hierarchicalMenu

![Example of the hierarchicalMenu widget][hierarchicalMenu]
Expand Down
40 changes: 24 additions & 16 deletions components/Pagination/Pagination.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Pagination extends React.Component {
this.props.setCurrentPage(pageNumber);
}

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

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

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

return (
<PaginationLink
Expand All @@ -37,55 +38,60 @@ class Pagination extends React.Component {
handleClick={handleClick}
key={label}
label={label}
url={url}
/>
);
}

previousPageLink(pager) {
previousPageLink(pager, createURL) {
var className = cx(bem('item-previous'), this.props.cssClasses.previous);
return this.pageLink({
ariaLabel: 'Previous',
className: className,
isDisabled: pager.isFirstPage(),
label: this.props.labels.previous,
pageNumber: pager.currentPage - 1
pageNumber: pager.currentPage - 1,
createURL
});
}

nextPageLink(pager) {
nextPageLink(pager, createURL) {
var className = cx(bem('item-next'), this.props.cssClasses.next);
return this.pageLink({
ariaLabel: 'Next',
className: className,
isDisabled: pager.isLastPage(),
label: this.props.labels.next,
pageNumber: pager.currentPage + 1
pageNumber: pager.currentPage + 1,
createURL
});
}

firstPageLink(pager) {
firstPageLink(pager, createURL) {
var className = cx(bem('item-first'), this.props.cssClasses.first);
return this.pageLink({
ariaLabel: 'First',
className: className,
isDisabled: pager.isFirstPage(),
label: this.props.labels.first,
pageNumber: 0
pageNumber: 0,
createURL
});
}

lastPageLink(pager) {
lastPageLink(pager, createURL) {
var className = cx(bem('item-last'), this.props.cssClasses.last);
return this.pageLink({
ariaLabel: 'Last',
className: className,
isDisabled: pager.isLastPage(),
label: this.props.labels.last,
pageNumber: pager.total - 1
pageNumber: pager.total - 1,
createURL
});
}

pages(pager) {
pages(pager, createURL) {
var pages = [];
var className = cx(bem('item-page'), this.props.cssClasses.item);

Expand All @@ -97,7 +103,8 @@ class Pagination extends React.Component {
className: className,
isActive: isActive,
label: pageNumber + 1,
pageNumber: pageNumber
pageNumber: pageNumber,
createURL
}));
});

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

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

return (
<ul className={cssClassesList}>
{this.props.showFirstLast ? this.firstPageLink(pager) : null}
{this.previousPageLink(pager)}
{this.pages(pager)}
{this.nextPageLink(pager)}
{this.props.showFirstLast ? this.lastPageLink(pager) : null}
{this.props.showFirstLast ? this.firstPageLink(pager, createURL) : null}
{this.previousPageLink(pager, createURL)}
{this.pages(pager, createURL)}
{this.nextPageLink(pager, createURL)}
{this.props.showFirstLast ? this.lastPageLink(pager, createURL) : null}
</ul>
);
}
Expand Down
7 changes: 4 additions & 3 deletions components/Pagination/PaginationLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ class PaginationLink extends React.Component {
}

render() {
var {className, label, ariaLabel, handleClick} = this.props;
var {className, label, ariaLabel, handleClick, url} = this.props;

return (
<li className={className}>
<a
ariaLabel={ariaLabel}
className={className}
dangerouslySetInnerHTML={{__html: label}}
href="#"
href={url}
onClick={handleClick}
></a>
</li>
Expand All @@ -33,7 +33,8 @@ PaginationLink.propTypes = {
label: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number
]).isRequired
]).isRequired,
url: React.PropTypes.string
};

module.exports = PaginationLink;
12 changes: 8 additions & 4 deletions components/RefinementList.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ class RefinementList extends React.Component {

_generateFacetItem(facetValue) {
var hasChildren = facetValue.data && facetValue.data.length > 0;
var subList = hasChildren && <RefinementList {...this.props} facetValues={facetValue.data} />;
var data = facetValue;

var subList = hasChildren ?
<RefinementList {...this.props} facetValues={facetValue.data} /> :
null;
if (this.props.createURL) {
data.url = this.props.createURL(facetValue[this.props.facetNameKey]);
}

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

Expand Down Expand Up @@ -78,13 +80,15 @@ class RefinementList extends React.Component {
render() {
return (
<div className={cx(this.props.cssClasses.list)}>
{this.props.facetValues.map(this._generateFacetItem, this)}
{this.props.facetValues.map(this._generateFacetItem, this)}
</div>
);
}
}

RefinementList.propTypes = {
Template: React.PropTypes.func,
createURL: React.PropTypes.func.isRequired,
cssClasses: React.PropTypes.shape({
item: React.PropTypes.oneOfType([
React.PropTypes.string,
Expand Down
Loading

0 comments on commit 23dd505

Please sign in to comment.