From 745ed8986373a7a2535151ec3b44362c9e2e0ff3 Mon Sep 17 00:00:00 2001 From: Alex S Date: Tue, 14 Feb 2017 03:43:46 +0100 Subject: [PATCH] fix(infinite-hits): disable load more button when no more pages (#1973) fixes #1971 --- docs/_includes/widget-jsdoc/infiniteHits.md | 68 +++++++++++++++++++ docs/documentation.md | 49 +++++++++++++ src/components/InfiniteHits.js | 9 ++- .../__tests__/infinite-hits-test.js | 28 +++++++- src/widgets/infinite-hits/infinite-hits.js | 13 ++-- 5 files changed, 159 insertions(+), 8 deletions(-) create mode 100644 docs/_includes/widget-jsdoc/infiniteHits.md diff --git a/docs/_includes/widget-jsdoc/infiniteHits.md b/docs/_includes/widget-jsdoc/infiniteHits.md new file mode 100644 index 0000000000..ae75c5d573 --- /dev/null +++ b/docs/_includes/widget-jsdoc/infiniteHits.md @@ -0,0 +1,68 @@ +

Parameters

+

+`options.container` + (string | DOMElement) +

+

CSS Selector or DOMElement to insert the widget

+

+`options.hitsPerPage` + Default:20(number) +

+

The number of hits to display per page

+

+`options.templates` + (Object) +

+

Templates to use for the widget

+

+`options.templates.empty` + Default:""(string | function) +

+

Template to use when there are no results.

+

+`options.templates.item` + Default:""(string | function) +

+

Template to use for each result. This template will receive an object containing a single record.

+

+`options.showMoreLabel` + Default:"Show more results"(string) +

+

label used on the show more button

+

+`options.transformData` + (Object) +

+

Method to change the object passed to the templates

+

+`options.transformData.empty` + (function) +

+

Method used to change the object passed to the `empty` template

+

+`options.transformData.item` + (function) +

+

Method used to change the object passed to the `item` template

+

+`options.cssClasses` + (Object) +

+

CSS classes to add

+

+`options.cssClasses.root` + (string | Array.<string>) +

+

CSS class to add to the wrapping element

+

+`options.cssClasses.empty` + (string | Array.<string>) +

+

CSS class to add to the wrapping element when no results

+

+`options.cssClasses.item` + (string | Array.<string>) +

+

CSS class to add to each result

+ +

* Required

diff --git a/docs/documentation.md b/docs/documentation.md index 27b5efceb0..3934c129f0 100644 --- a/docs/documentation.md +++ b/docs/documentation.md @@ -423,6 +423,55 @@ and
+#### Infinite hits + +
+ + +The infinite hits widget is like the hits widget but instead of relying on an external pagination widget, it displays a button to load more results.
+It will display the results as one continuous long page. Its usage is particularly recommended in a mobile context. Even though it is not incompatible, it does not go well with the pagination widget.
+It accepts a [Mustache](https://mustache.github.io/) template string or a function returning a string. See the [templates](#templates) section. +{:.description} + +
+
+{% highlight javascript %} +{% raw %} +search.addWidget( + instantsearch.widgets.infiniteHits({ + container: '#infinite-hits-container', + templates: { + empty: 'No results', + item: 'Hit {{objectID}}: {{{_highlightResult.name.value}}}' + }, + hitsPerPage: 3 + }) +); +{% endraw %} +{% endhighlight %} +
+
+{% highlight javascript %} +{% raw %} +instantsearch.widgets.infiniteHits(options); +{% endraw %} +{% endhighlight %} + +{% include widget-jsdoc/infiniteHits.md %} + +
+
+For better control over what kind of data is returned, we suggest you configure the +[attributeToRetrieve](https://www.algolia.com/doc/rest#param-attributesToRetrieve) +and +[attributeToHighlight](https://www.algolia.com/doc/rest#param-attributesToHighlight) of your index. +
+
+ +
+ +
+ #### hitsPerPageSelector diff --git a/src/components/InfiniteHits.js b/src/components/InfiniteHits.js index a8b29c4c74..7f783bc478 100644 --- a/src/components/InfiniteHits.js +++ b/src/components/InfiniteHits.js @@ -5,6 +5,10 @@ class InfiniteHits extends React.Component { render() { const {cssClasses, hits, results, showMore, showMoreLabel, templateProps} = this.props; + const btn = this.props.isLastPage ? + : + ; + return (
-
+
+ {btn} +
); } @@ -32,6 +38,7 @@ InfiniteHits.propTypes = { showMore: React.PropTypes.function, showMoreLabel: React.PropTypes.string, templateProps: React.PropTypes.object.isRequired, + isLastPage: React.PropTypes.bool.isRequired, }; export default InfiniteHits; diff --git a/src/widgets/infinite-hits/__tests__/infinite-hits-test.js b/src/widgets/infinite-hits/__tests__/infinite-hits-test.js index 5dafc27402..5d01dd17a9 100644 --- a/src/widgets/infinite-hits/__tests__/infinite-hits-test.js +++ b/src/widgets/infinite-hits/__tests__/infinite-hits-test.js @@ -67,6 +67,27 @@ describe('infiniteHits()', () => { expect(ReactDOM.render.secondCall.args[1]).toEqual(container); }); + it('if it is the last page, then the props should contain isLastPage true', () => { + props = getProps(); + const state = {page: 0}; + widget.render({ + results: {...results, page: 0, nbPages: 2}, + state, + }); + widget.render({ + results: {...results, page: 1, nbPages: 2}, + state, + }); + + expect(ReactDOM.render.calledTwice).toBe(true, 'ReactDOM.render called twice'); + const propsWithIsLastPageFalse = {...(getProps({...results, page: 0, nbPages: 2})), isLastPage: false}; + expect(ReactDOM.render.firstCall.args[0]).toEqualJSX(); + expect(ReactDOM.render.firstCall.args[1]).toEqual(container); + const propsWithIsLastPageTrue = {...(getProps({...results, page: 1, nbPages: 2})), isLastPage: true}; + expect(ReactDOM.render.secondCall.args[0]).toEqualJSX(); + expect(ReactDOM.render.secondCall.args[1]).toEqual(container); + }); + it('does not accept both item and allItems templates', () => { expect(infiniteHits.bind({container, templates: {item: '', allItems: ''}})).toThrow(); }); @@ -85,10 +106,10 @@ describe('infiniteHits()', () => { infiniteHits.__ResetDependency__('defaultTemplates'); }); - function getProps() { + function getProps(otherResults) { return { - hits: results.hits, - results, + hits: (otherResults || results).hits, + results: otherResults || results, templateProps, cssClasses: { root: 'ais-infinite-hits root cx', @@ -98,6 +119,7 @@ describe('infiniteHits()', () => { }, showMore: () => {}, showMoreLabel: 'Show more results', + isLastPage: false, }; } }); diff --git a/src/widgets/infinite-hits/infinite-hits.js b/src/widgets/infinite-hits/infinite-hits.js index 22d6841a3a..a90d5ae600 100644 --- a/src/widgets/infinite-hits/infinite-hits.js +++ b/src/widgets/infinite-hits/infinite-hits.js @@ -13,19 +13,20 @@ const bem = bemHelper('ais-infinite-hits'); /** * Display the list of results (hits) from the current search - * @function hits + * @function infiniteHits * @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget * @param {number} [options.hitsPerPage=20] The number of hits to display per page [*] * @param {Object} [options.templates] Templates to use for the widget - * @param {string|Function} [options.templates.empty=''] Template to use when there are no results. - * @param {string|Function} [options.templates.item=''] Template to use for each result. This template will receive an object containing a single record. + * @param {string|Function} [options.templates.empty=""] Template to use when there are no results. + * @param {string|Function} [options.templates.item=""] Template to use for each result. This template will receive an object containing a single record. + * @param {string} [options.showMoreLabel="Show more results"] label used on the show more button * @param {Object} [options.transformData] Method to change the object passed to the templates * @param {Function} [options.transformData.empty] Method used to change the object passed to the `empty` template * @param {Function} [options.transformData.item] Method used to change the object passed to the `item` template * @param {Object} [options.cssClasses] CSS classes to add * @param {string|string[]} [options.cssClasses.root] CSS class to add to the wrapping element * @param {string|string[]} [options.cssClasses.empty] CSS class to add to the wrapping element when no results - * @param {string|string[]} [options.7cssClasses.item] CSS class to add to each result + * @param {string|string[]} [options.cssClasses.item] CSS class to add to each result * @return {Object} */ const usage = ` @@ -34,6 +35,7 @@ infiniteHits({ container, [ cssClasses.{root,empty,item}={} ], [ templates.{empty,item} | templates.{empty} ], + [ showMoreLabel="Show more results" ] [ transformData.{empty,item} | transformData.{empty} ], [ hitsPerPage=20 ] })`; @@ -80,6 +82,8 @@ function infiniteHits({ hitsCache = [...hitsCache, ...results.hits]; + const isLastPage = results.nbPages <= results.page + 1; + ReactDOM.render( , containerNode