From 23dd5056ff5208ee1c75266edb8407e0988a7d73 Mon Sep 17 00:00:00 2001 From: Alexandre Stanislawski Date: Tue, 13 Oct 2015 11:35:28 +0200 Subject: [PATCH] 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. --- README.md | 116 +++++++++------ components/Pagination/Pagination.js | 40 +++--- components/Pagination/PaginationLink.js | 7 +- components/RefinementList.js | 12 +- components/Template.js | 64 ++++----- example/app.js | 11 +- example/templates/and.html | 2 +- example/templates/category.html | 2 +- index.js | 3 +- lib/InstantSearch.js | 58 +++++--- lib/__tests__/InstantSearch-test.js | 14 +- {widgets => lib}/url-sync.js | 27 +++- npm-shrinkwrap.json | 134 +++++++++--------- package.json | 2 +- widgets/hierarchicalMenu.js | 5 +- widgets/menu.js | 5 +- .../pagination/__tests__/pagination-test.js | 1 + widgets/pagination/pagination.js | 3 +- widgets/refinement-list/refinement-list.js | 3 +- widgets/toggle/__tests__/toggle-test.js | 3 +- widgets/toggle/toggle.js | 3 +- 21 files changed, 291 insertions(+), 224 deletions(-) rename {widgets => lib}/url-sync.js (84%) diff --git a/README.md b/README.md index cd448547d5..38a1c63847 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 @@ -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] diff --git a/components/Pagination/Pagination.js b/components/Pagination/Pagination.js index 9283b73656..2867c607ee 100644 --- a/components/Pagination/Pagination.js +++ b/components/Pagination/Pagination.js @@ -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); @@ -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 ( ); } - 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); @@ -97,7 +103,8 @@ class Pagination extends React.Component { className: className, isActive: isActive, label: pageNumber + 1, - pageNumber: pageNumber + pageNumber: pageNumber, + createURL })); }); @@ -112,14 +119,15 @@ class Pagination extends React.Component { }); var cssClassesList = cx(bem(null), this.props.cssClasses.root); + var createURL = this.props.createURL; return ( ); } diff --git a/components/Pagination/PaginationLink.js b/components/Pagination/PaginationLink.js index e05506b0d9..76df56fa6b 100644 --- a/components/Pagination/PaginationLink.js +++ b/components/Pagination/PaginationLink.js @@ -7,7 +7,7 @@ class PaginationLink extends React.Component { } render() { - var {className, label, ariaLabel, handleClick} = this.props; + var {className, label, ariaLabel, handleClick, url} = this.props; return (
  • @@ -15,7 +15,7 @@ class PaginationLink extends React.Component { ariaLabel={ariaLabel} className={className} dangerouslySetInnerHTML={{__html: label}} - href="#" + href={url} onClick={handleClick} >
  • @@ -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; diff --git a/components/RefinementList.js b/components/RefinementList.js index 60c073ea61..a3f4966229 100644 --- a/components/RefinementList.js +++ b/components/RefinementList.js @@ -11,10 +11,12 @@ class RefinementList extends React.Component { _generateFacetItem(facetValue) { var hasChildren = facetValue.data && facetValue.data.length > 0; + var subList = hasChildren && ; + var data = facetValue; - var subList = hasChildren ? - : - null; + if (this.props.createURL) { + data.url = this.props.createURL(facetValue[this.props.facetNameKey]); + } var templateData = {...facetValue, cssClasses: this.props.cssClasses}; @@ -78,13 +80,15 @@ class RefinementList extends React.Component { render() { return (
    - {this.props.facetValues.map(this._generateFacetItem, this)} + {this.props.facetValues.map(this._generateFacetItem, this)}
    ); } } RefinementList.propTypes = { + Template: React.PropTypes.func, + createURL: React.PropTypes.func.isRequired, cssClasses: React.PropTypes.shape({ item: React.PropTypes.oneOfType([ React.PropTypes.string, diff --git a/components/Template.js b/components/Template.js index b02efe3c53..45080671af 100644 --- a/components/Template.js +++ b/components/Template.js @@ -1,12 +1,17 @@ var React = require('react'); +var mapValues = require('lodash/object/mapValues'); +var curry = require('lodash/function/curry'); +var hogan = require('hogan.js'); class Template extends React.Component { render() { + var compileOptions = this.props.useCustomCompileOptions[this.props.templateKey] ? + this.props.templatesConfig.compileOptions : + {}; + var content = renderTemplate({ template: this.props.templates[this.props.templateKey], - compileOptions: this.props.useCustomCompileOptions[this.props.templateKey] ? - this.props.templatesConfig.compileOptions : - {}, + compileOptions: compileOptions, helpers: this.props.templatesConfig.helpers, data: transformData(this.props.transformData, this.props.templateKey, this.props.data) }); @@ -76,43 +81,32 @@ function transformData(fn, templateKey, originalData) { } function renderTemplate({template, compileOptions, helpers, data}) { - var hogan = require('hogan.js'); - var forOwn = require('lodash/object/forOwn'); - var content; + let isTemplateString = typeof template === 'string'; + let isTemplateFunction = typeof template === 'function'; - if (typeof template !== 'string' && typeof template !== 'function') { + if (!isTemplateString && !isTemplateFunction) { throw new Error('Template must be `string` or `function`'); + } else if (isTemplateFunction) { + return template(data); + } else { + let transformedHelpers = transformHelpersToHogan(helpers, compileOptions, data); + let preparedData = {...data, helpers: transformedHelpers}; + return hogan.compile(template, compileOptions).render(preparedData); } +} - if (typeof template === 'function') { - content = template(data); - } - - if (typeof template === 'string') { - data = addTemplateHelpersToData(data); - - content = hogan.compile(template, compileOptions).render(data); - } - - // We add all our template helper methods to the template as lambdas. Note - // that lambdas in Mustache are supposed to accept a second argument of - // `render` to get the rendered value, not the literal `{{value}}`. But - // this is currently broken (see - // https://github.com/twitter/hogan.js/issues/222). - function addTemplateHelpersToData(templateData) { - templateData.helpers = {}; - forOwn(helpers, (method, name) => { - templateData.helpers[name] = function() { - return (text) => { - var render = (value) => hogan.compile(value, compileOptions).render(this); - return method.call(this, text, render); - }; - }; +// We add all our template helper methods to the template as lambdas. Note +// that lambdas in Mustache are supposed to accept a second argument of +// `render` to get the rendered value, not the literal `{{value}}`. But +// this is currently broken (see +// https://github.com/twitter/hogan.js/issues/222). +function transformHelpersToHogan(helpers, compileOptions, data) { + return mapValues(helpers, (method) => { + return curry(function(text) { + var render = (value) => hogan.compile(value, compileOptions).render(this); + return method.call(data, text, render); }); - return data; - } - - return content; + }); } module.exports = Template; diff --git a/example/app.js b/example/app.js index 5af0c79599..e11fe36922 100644 --- a/example/app.js +++ b/example/app.js @@ -4,14 +4,11 @@ var instantsearch = require('../index'); var search = instantsearch({ appId: 'latency', apiKey: '6be0576ff61c053d5f9a3225e2a90f76', - indexName: 'instant_search' -}); - -search.addWidget( - instantsearch.widgets.urlSync({ + indexName: 'instant_search', + urlSync: { useHash: true - }) -); + } +}); search.addWidget( instantsearch.widgets.searchBox({ diff --git a/example/templates/and.html b/example/templates/and.html index 97848b4524..63a74142db 100644 --- a/example/templates/and.html +++ b/example/templates/and.html @@ -1,4 +1,4 @@ - + {{name}} {{count}} diff --git a/example/templates/category.html b/example/templates/category.html index 802c0cdda4..5915a071d6 100644 --- a/example/templates/category.html +++ b/example/templates/category.html @@ -1 +1 @@ -{{name}} {{count}} +{{name}} {{count}} diff --git a/index.js b/index.js index a518f320bb..76658889c1 100644 --- a/index.js +++ b/index.js @@ -17,8 +17,7 @@ instantsearch.widgets = { searchBox: require('./widgets/search-box'), rangeSlider: require('./widgets/range-slider'), stats: require('./widgets/stats/stats'), - toggle: require('./widgets/toggle/toggle'), - urlSync: require('./widgets/url-sync') + toggle: require('./widgets/toggle/toggle') }; instantsearch.version = require('./lib/version.js'); diff --git a/lib/InstantSearch.js b/lib/InstantSearch.js index aad05d219b..32f7675142 100644 --- a/lib/InstantSearch.js +++ b/lib/InstantSearch.js @@ -7,13 +7,18 @@ var union = require('lodash/array/union'); var EventEmitter = require('events').EventEmitter; +var urlSyncWidget = require('./url-sync'); + +function defaultCreateURL() { return '#'; } + class InstantSearch extends EventEmitter { constructor({ appId = null, apiKey = null, indexName = null, numberLocale = 'en-EN', - searchParameters = {} + searchParameters = {}, + urlSync = null }) { super(); if (appId === null || apiKey === null || indexName === null) { @@ -41,6 +46,7 @@ Usage: instantsearch({ }, compileOptions: {} }; + this.urlSync = urlSync; } addWidget(widgetDefinition) { @@ -49,32 +55,22 @@ Usage: instantsearch({ } start() { - this.searchParameters = this.widgets.sort(function initLastSort(a, b) { - // Widgets with the __initLast tag should provide configuration last - if (a.__initLast === b.__initLast) return 0; - return a.__initLast ? 1 : -1; - }).reduce(function(configuration, widgetDefinition) { - if (!widgetDefinition.getConfiguration) return configuration; - - // Update searchParameters with the configuration from the widgets - var partialConfiguration = widgetDefinition.getConfiguration(configuration); - return merge( - {}, - configuration, - partialConfiguration, - (a, b) => { - if (Array.isArray(a)) { - return union(a, b); - } - } - ); - }, this.searchParameters); + if (!this.widgets) throw new Error('No widgets were added to instantsearch.js'); + + if (this.urlSync) { + let syncWidget = urlSyncWidget(this.urlSync); + this.createURL = syncWidget.createURL.bind(syncWidget); + this.widgets.push(syncWidget); + } else this.createURL = defaultCreateURL; + + this.searchParameters = this.widgets.reduce(enhanceConfiguration, this.searchParameters); var helper = algoliasearchHelper( this.client, this.searchParameters.index || this.indexName, this.searchParameters ); + this.helper = helper; this._init(helper.state, helper); @@ -92,7 +88,8 @@ Usage: instantsearch({ templatesConfig: this.templatesConfig, results, state, - helper + helper, + createURL: this.createURL }); }, this); this.emit('render'); @@ -107,4 +104,21 @@ Usage: instantsearch({ } } +function enhanceConfiguration(configuration, widgetDefinition) { + if (!widgetDefinition.getConfiguration) return configuration; + + // Update searchParameters with the configuration from the widgets + var partialConfiguration = widgetDefinition.getConfiguration(configuration); + return merge( + {}, + configuration, + partialConfiguration, + (a, b) => { + if (Array.isArray(a)) { + return union(a, b); + } + } + ); +} + module.exports = InstantSearch; diff --git a/lib/__tests__/InstantSearch-test.js b/lib/__tests__/InstantSearch-test.js index ddd1586047..09a9744be6 100644 --- a/lib/__tests__/InstantSearch-test.js +++ b/lib/__tests__/InstantSearch-test.js @@ -122,6 +122,7 @@ describe('InstantSearch lifecycle', () => { expect(widget.render.calledOnce).toBe(true, 'widget.render called once'); expect(widget.render.args[0]) .toEqual([{ + createURL: search.createURL, results, state: helper.state, helper, @@ -132,7 +133,7 @@ describe('InstantSearch lifecycle', () => { }); }); - context('when we have 5 widgets, the third one having __initLast = true', () => { + context('when we have 5 widgets', () => { var widgets; beforeEach(() => { @@ -142,8 +143,6 @@ describe('InstantSearch lifecycle', () => { getConfiguration: sinon.stub().returns({values: [widgetIndex]}) }; - if (widgetIndex === 2) widget.__initLast = true; - return widget; }); widgets.forEach(search.addWidget, search); @@ -152,7 +151,6 @@ describe('InstantSearch lifecycle', () => { it('calls widget[x].getConfiguration in the orders the widgets were added', () => { let order = widgets - .filter((widget, widgetIndex) => widgetIndex !== 2) .every((widget, widgetIndex, filteredWidgets) => { if (widgetIndex === 0) { return widget.getConfiguration.calledOnce && @@ -166,14 +164,8 @@ describe('InstantSearch lifecycle', () => { expect(order).toBe(true); }); - it('calls widget[2].getConfiguration before any other widget, because of __initLast', () => { - let initLastWidget = widgets[2]; - let lastWidget = widgets[widgets.length - 1]; - expect(initLastWidget.getConfiguration.calledAfter(lastWidget.getConfiguration)).toBe(true); - }); - it('recursevly merges searchParameters.values array', () => { - expect(algoliasearchHelper.args[0][2].values).toEqual([-2, -1, 0, 1, 3, 4, 2]); + expect(algoliasearchHelper.args[0][2].values).toEqual([-2, -1, 0, 1, 2, 3, 4]); }); }); diff --git a/widgets/url-sync.js b/lib/url-sync.js similarity index 84% rename from widgets/url-sync.js rename to lib/url-sync.js index e4c04034c6..bf59a08cd8 100644 --- a/widgets/url-sync.js +++ b/lib/url-sync.js @@ -1,4 +1,5 @@ -var AlgoliaSearchHelper = require('algoliasearch-helper').AlgoliaSearchHelper; +var algoliasearchHelper = require('algoliasearch-helper'); +var AlgoliaSearchHelper = algoliasearchHelper.AlgoliaSearchHelper; var majorVersionNumber = require('../lib/version.js').split('.')[0]; var isEqual = require('lodash/lang/isEqual'); @@ -33,10 +34,13 @@ var hashUrlUtils = { window.addEventListener('hashchange', cb); }, pushState: function(qs) { - window.location.assign(document.location.search + this.character + qs); + window.location.assign(this.createURL(qs)); }, replaceState: function(qs) { - window.location.replace(document.location.search + this.character + qs); + window.location.replace(this.createURL(qs)); + }, + createURL: function(qs) { + return document.location.search + this.character + qs; }, readUrl: function() { return window.location.hash.slice(1); @@ -53,10 +57,13 @@ var modernUrlUtils = { window.addEventListener('popstate', cb); }, pushState: function(qs) { - window.history.pushState(null, '', this.character + qs + document.location.hash); + window.history.pushState(null, '', this.createURL(qs)); }, replaceState: function(qs) { - window.history.replaceState(null, '', this.character + qs + document.location.hash); + window.history.replaceState(null, '', this.createURL(qs)); + }, + createURL: function(qs) { + return this.character + qs + document.location.hash; }, readUrl: function() { return window.location.search.slice(1); @@ -133,6 +140,16 @@ class URLSync { this.urlUtils.pushState(qs); } } + + createURL(state) { + var currentQueryString = this.urlUtils.readUrl(); + var filteredState = state.filter(this.trackedParameters); + var foreignConfig = algoliasearchHelper.url.getUnrecognizedParametersInQueryString(currentQueryString); + // Add instantsearch version to reconciliate old url with newer versions + foreignConfig.is_v = majorVersionNumber; + + return this.urlUtils.createURL(algoliasearchHelper.url.getQueryStringFromState(filteredState)); + } } /** diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index feab948529..fa745ae73d 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -106,7 +106,7 @@ }, "algoliasearch-helper": { "version": "2.6.2", - "from": "algoliasearch-helper@>=2.5.0 <3.0.0", + "from": "algoliasearch-helper@>=2.6.2 <3.0.0", "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-2.6.2.tgz", "dependencies": { "lodash-compat": { @@ -767,9 +767,9 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.1.tgz" }, "core-js": { - "version": "1.2.1", + "version": "1.2.2", "from": "core-js@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.1.tgz" + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.2.tgz" }, "debug": { "version": "2.2.0", @@ -1016,7 +1016,7 @@ }, "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.0.0 <1.0.0", + "from": "mkdirp@>=0.5.0 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "dependencies": { "minimist": { @@ -1117,7 +1117,7 @@ "dependencies": { "align-text": { "version": "0.1.3", - "from": "align-text@>=0.1.0 <0.2.0", + "from": "align-text@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz", "dependencies": { "kind-of": { @@ -1153,7 +1153,7 @@ "dependencies": { "align-text": { "version": "0.1.3", - "from": "align-text@>=0.1.0 <0.2.0", + "from": "align-text@>=0.1.1 <0.2.0", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz", "dependencies": { "kind-of": { @@ -1343,7 +1343,7 @@ }, "source-map": { "version": "0.4.4", - "from": "source-map@>=0.4.0 <0.5.0", + "from": "source-map@>=0.4.1 <0.5.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", "dependencies": { "amdefine": { @@ -1567,9 +1567,9 @@ "resolved": "https://registry.npmjs.org/babel-plugin-rewire/-/babel-plugin-rewire-0.1.22.tgz" }, "classnames": { - "version": "2.1.5", + "version": "2.2.0", "from": "classnames@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.1.5.tgz" + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.0.tgz" }, "conventional-changelog": { "version": "0.5.0", @@ -1808,7 +1808,7 @@ }, "normalize-package-data": { "version": "2.3.4", - "from": "normalize-package-data@>=2.3.2 <3.0.0", + "from": "normalize-package-data@>=2.3.0 <3.0.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.4.tgz", "dependencies": { "is-builtin-module": { @@ -2510,9 +2510,9 @@ } }, "concat-stream": { - "version": "1.5.0", + "version": "1.5.1", "from": "concat-stream@>=1.4.6 <2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.1.tgz", "dependencies": { "inherits": { "version": "2.0.1", @@ -2609,7 +2609,7 @@ }, "es5-ext": { "version": "0.10.8", - "from": "es5-ext@>=0.10.6 <0.11.0", + "from": "es5-ext@>=0.10.8 <0.11.0", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.8.tgz" }, "es6-iterator": { @@ -2691,9 +2691,9 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.5.tgz" }, "estraverse": { - "version": "4.1.0", + "version": "4.1.1", "from": "estraverse@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.0.tgz" + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz" }, "estraverse-fb": { "version": "1.3.1", @@ -3529,9 +3529,9 @@ "resolved": "https://registry.npmjs.org/eslint-config-algolia/-/eslint-config-algolia-4.1.0.tgz" }, "eslint-plugin-react": { - "version": "3.5.1", + "version": "3.6.2", "from": "eslint-plugin-react@>=3.5.1 <4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-3.5.1.tgz" + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-3.6.2.tgz" }, "events": { "version": "1.1.0", @@ -3625,7 +3625,7 @@ "dependencies": { "loader-utils": { "version": "0.2.11", - "from": "loader-utils@>=0.2.0 <0.3.0", + "from": "loader-utils@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.11.tgz", "dependencies": { "big.js": { @@ -4836,9 +4836,9 @@ } }, "core-js": { - "version": "1.2.1", + "version": "1.2.2", "from": "core-js@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.1.tgz" + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.2.tgz" }, "di": { "version": "0.0.1", @@ -5717,7 +5717,7 @@ }, "loader-utils": { "version": "0.2.11", - "from": "loader-utils@>=0.2.0 <0.3.0", + "from": "loader-utils@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.11.tgz", "dependencies": { "big.js": { @@ -5829,7 +5829,7 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } @@ -6198,14 +6198,14 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } }, "xtend": { "version": "4.0.0", - "from": "xtend@>=4.0.0 <4.1.0", + "from": "xtend@>=4.0.0 <4.1.0-0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" } } @@ -6779,7 +6779,7 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } @@ -6912,7 +6912,7 @@ "dependencies": { "mkdirp": { "version": "0.5.1", - "from": "mkdirp@>=0.0.0 <1.0.0", + "from": "mkdirp@>=0.5.1 <0.6.0", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "dependencies": { "minimist": { @@ -7463,16 +7463,16 @@ "from": "builtin-modules@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.0.tgz" }, - "builtins": { - "version": "0.0.7", - "from": "builtins@0.0.7", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz" - }, "caseless": { "version": "0.11.0", "from": "caseless@>=0.11.0 <0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" }, + "builtins": { + "version": "0.0.7", + "from": "builtins@0.0.7", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-0.0.7.tgz" + }, "chalk": { "version": "1.1.1", "from": "chalk@1.1.1", @@ -7627,16 +7627,16 @@ "from": "is-property@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" }, - "is-relative": { - "version": "0.1.3", - "from": "is-relative@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz" - }, "isarray": { "version": "0.0.1", "from": "isarray@0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, + "is-relative": { + "version": "0.1.3", + "from": "is-relative@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz" + }, "isstream": { "version": "0.1.2", "from": "isstream@>=0.1.1 <0.2.0", @@ -7767,16 +7767,16 @@ "from": "lodash.isarray@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz" }, - "lodash.istypedarray": { - "version": "3.0.2", - "from": "lodash.istypedarray@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.2.tgz" - }, "lodash.keys": { "version": "3.1.2", "from": "lodash.keys@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" }, + "lodash.istypedarray": { + "version": "3.0.2", + "from": "lodash.istypedarray@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.2.tgz" + }, "lodash.pad": { "version": "3.1.1", "from": "lodash.pad@>=3.0.0 <4.0.0", @@ -7787,16 +7787,16 @@ "from": "lodash.padleft@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/lodash.padleft/-/lodash.padleft-3.1.1.tgz" }, - "lodash.pairs": { - "version": "3.0.1", - "from": "lodash.pairs@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz" - }, "lodash.padright": { "version": "3.1.1", "from": "lodash.padright@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/lodash.padright/-/lodash.padright-3.1.1.tgz" }, + "lodash.pairs": { + "version": "3.0.1", + "from": "lodash.pairs@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz" + }, "lodash.repeat": { "version": "3.0.1", "from": "lodash.repeat@>=3.0.0 <4.0.0", @@ -7981,16 +7981,16 @@ "from": "util-deprecate@>=1.0.1 <1.1.0", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz" }, - "util-extend": { - "version": "1.0.1", - "from": "util-extend@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.1.tgz" - }, "validate-npm-package-license": { "version": "3.0.1", "from": "validate-npm-package-license@>=3.0.1 <4.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz" }, + "util-extend": { + "version": "1.0.1", + "from": "util-extend@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.1.tgz" + }, "wcwidth": { "version": "1.0.0", "from": "wcwidth@>=1.0.0 <2.0.0", @@ -8420,7 +8420,7 @@ }, "debug": { "version": "2.2.0", - "from": "debug@2.2.0", + "from": "debug@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "dependencies": { "ms": { @@ -9101,7 +9101,7 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.0 <3.0.0", + "from": "inherits@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" }, "ini": { @@ -9701,19 +9701,19 @@ "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.3.2.tgz", "dependencies": { "core-js": { - "version": "1.2.1", + "version": "1.2.2", "from": "core-js@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.1.tgz" + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.2.tgz" }, "loose-envify": { - "version": "1.0.0", + "version": "1.1.0", "from": "loose-envify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.1.0.tgz", "dependencies": { "js-tokens": { - "version": "1.0.1", + "version": "1.0.2", "from": "js-tokens@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz" + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.2.tgz" } } }, @@ -9787,7 +9787,7 @@ "dependencies": { "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" } } @@ -9811,7 +9811,7 @@ "dependencies": { "loader-utils": { "version": "0.2.11", - "from": "loader-utils@>=0.2.0 <0.3.0", + "from": "loader-utils@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.11.tgz", "dependencies": { "big.js": { @@ -10033,7 +10033,7 @@ }, "xtend": { "version": "4.0.0", - "from": "xtend@>=4.0.0 <4.1.0", + "from": "xtend@>=4.0.0 <4.1.0-0", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" } } @@ -10086,7 +10086,7 @@ "dependencies": { "loader-utils": { "version": "0.2.11", - "from": "loader-utils@>=0.2.0 <0.3.0", + "from": "loader-utils@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.11.tgz", "dependencies": { "big.js": { @@ -10880,9 +10880,9 @@ } }, "webpack-dev-server": { - "version": "1.12.0", + "version": "1.12.1", "from": "webpack-dev-server@>=1.12.0 <2.0.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-1.12.0.tgz", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-1.12.1.tgz", "dependencies": { "compression": { "version": "1.6.0", @@ -10932,7 +10932,7 @@ }, "debug": { "version": "2.2.0", - "from": "debug@2.2.0", + "from": "debug@>=2.2.0 <2.3.0", "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", "dependencies": { "ms": { @@ -11276,7 +11276,7 @@ "dependencies": { "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" }, "statuses": { diff --git a/package.json b/package.json index d981c00572..610be665d5 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ }, "dependencies": { "algoliasearch": "^3.8.0", - "algoliasearch-helper": "^2.5.0", + "algoliasearch-helper": "^2.6.2", "classnames": "^2.1.5", "events": "^1.1.0", "hogan.js": "^3.0.2", diff --git a/widgets/hierarchicalMenu.js b/widgets/hierarchicalMenu.js index b53a54ec90..e53ad6e27c 100644 --- a/widgets/hierarchicalMenu.js +++ b/widgets/hierarchicalMenu.js @@ -8,7 +8,7 @@ var RefinementList = autoHide(headerFooter(require('../components/RefinementList var defaultTemplates = { header: '', - item: '{{name}} {{count}}', + item: '{{name}} {{count}}', footer: '' }; @@ -66,7 +66,7 @@ function hierarchicalMenu({ separator }] }), - render: function({results, helper, templatesConfig}) { + render: function({results, helper, templatesConfig, createURL, state}) { var facetValues = getFacetValues(results, hierarchicalFacetName, sortBy); var templateProps = utils.prepareTemplateProps({ @@ -78,6 +78,7 @@ function hierarchicalMenu({ ReactDOM.render( createURL(state.toggleRefinement(hierarchicalFacetName, facetValue))} cssClasses={cssClasses} facetNameKey="path" facetValues={facetValues} diff --git a/widgets/menu.js b/widgets/menu.js index 59eb3666d7..e260f2f71a 100644 --- a/widgets/menu.js +++ b/widgets/menu.js @@ -8,7 +8,7 @@ var RefinementList = autoHide(headerFooter(require('../components/RefinementList var defaultTemplates = { header: '', - item: '{{name}} {{count}}', + item: '{{name}} {{count}}', footer: '' }; @@ -62,7 +62,7 @@ function menu({ attributes: [facetName] }] }), - render: function({results, helper, templatesConfig}) { + render: function({results, helper, templatesConfig, state, createURL}) { var facetValues = getFacetValues(results, hierarchicalFacetName, sortBy, limit); var templateProps = utils.prepareTemplateProps({ @@ -74,6 +74,7 @@ function menu({ ReactDOM.render( createURL(state.toggleRefinement(hierarchicalFacetName, facetValue))} cssClasses={cssClasses} facetValues={facetValues} hasResults={facetValues.length > 0} diff --git a/widgets/pagination/__tests__/pagination-test.js b/widgets/pagination/__tests__/pagination-test.js index e8fdc7385f..b27d159782 100644 --- a/widgets/pagination/__tests__/pagination-test.js +++ b/widgets/pagination/__tests__/pagination-test.js @@ -99,6 +99,7 @@ describe('pagination()', () => { padding: 3, setCurrentPage: () => {}, showFirstLast: true, + createURL: () => '#', ...extraProps }; } diff --git a/widgets/pagination/pagination.js b/widgets/pagination/pagination.js index 22069bfa26..eb6146da70 100644 --- a/widgets/pagination/pagination.js +++ b/widgets/pagination/pagination.js @@ -68,7 +68,7 @@ function pagination({ helper.search(); }, - render: function({results, helper}) { + render: function({results, helper, createURL, state}) { var currentPage = results.page; var nbPages = results.nbPages; var nbHits = results.nbHits; @@ -81,6 +81,7 @@ function pagination({ var Pagination = autoHide(require('../../components/Pagination/Pagination.js')); ReactDOM.render( createURL(state.setPage(page))} cssClasses={cssClasses} currentPage={currentPage} hasResults={hasResults} diff --git a/widgets/refinement-list/refinement-list.js b/widgets/refinement-list/refinement-list.js index 555849af20..8eb6acadc1 100644 --- a/widgets/refinement-list/refinement-list.js +++ b/widgets/refinement-list/refinement-list.js @@ -76,7 +76,7 @@ function refinementList({ return widgetConfiguration; }, - render: function({results, helper, templatesConfig}) { + render: function({results, helper, templatesConfig, state, createURL}) { var templateProps = utils.prepareTemplateProps({ transformData, defaultTemplates, @@ -101,6 +101,7 @@ function refinementList({ ReactDOM.render( createURL(state.toggleRefinement(facetName, facetValue))} cssClasses={cssClasses} facetValues={facetValues} hasResults={facetValues.length > 0} diff --git a/widgets/toggle/__tests__/toggle-test.js b/widgets/toggle/__tests__/toggle-test.js index 652a2030b6..b33723f16b 100644 --- a/widgets/toggle/__tests__/toggle-test.js +++ b/widgets/toggle/__tests__/toggle-test.js @@ -91,7 +91,8 @@ describe('toggle()', () => { cssClasses: {}, hideWhenNoResults: true, templateProps, - toggleRefinement: function() {} + toggleRefinement: function() {}, + createURL: () => '#' }; }); diff --git a/widgets/toggle/toggle.js b/widgets/toggle/toggle.js index 40d7281c89..eac645e987 100644 --- a/widgets/toggle/toggle.js +++ b/widgets/toggle/toggle.js @@ -51,7 +51,7 @@ function toggle({ getConfiguration: () => ({ facets: [facetName] }), - render: function({helper, results, templatesConfig}) { + render: function({helper, results, templatesConfig, state, createURL}) { var isRefined = helper.hasRefinements(facetName); var values = find(results.getFacetValues(facetName), {name: isRefined.toString()}); @@ -70,6 +70,7 @@ function toggle({ ReactDOM.render( createURL(state.toggleRefinement(facetName, facetValue.isRefined))} cssClasses={cssClasses} facetValues={[facetValue]} hasResults={results.hits.length > 0}