diff --git a/src/connectors/infinite-hits/connectInfiniteHits.js b/src/connectors/infinite-hits/connectInfiniteHits.js new file mode 100644 index 0000000000..c39fe3f49c --- /dev/null +++ b/src/connectors/infinite-hits/connectInfiniteHits.js @@ -0,0 +1,107 @@ +import { + bemHelper, + prepareTemplateProps, + getContainerNode, +} from '../../lib/utils.js'; +import cx from 'classnames'; +import defaultTemplates from './defaultTemplates.js'; + +const bem = bemHelper('ais-infinite-hits'); + +/** + * Display the list of results (hits) from the current search + * @function hits + * @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 {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 + * @return {Object} + */ +const usage = ` +Usage: +infiniteHits({ + container, + [ cssClasses.{root,empty,item}={} ], + [ templates.{empty,item} | templates.{empty} ], + [ transformData.{empty,item} | transformData.{empty} ], + [ hitsPerPage=20 ] +})`; +const connectInfiniteHits = infiniteHitsRendering => ({ + container, + cssClasses: userCssClasses = {}, + showMoreLabel = 'Show more results', + templates = defaultTemplates, + transformData, + hitsPerPage = 20, +} = {}) => { + if (!container) { + throw new Error(`Must provide a container.${usage}`); + } + + const containerNode = getContainerNode(container); + const cssClasses = { + root: cx(bem(null), userCssClasses.root), + item: cx(bem('item'), userCssClasses.item), + empty: cx(bem(null, 'empty'), userCssClasses.empty), + showmore: cx(bem('showmore'), userCssClasses.showmore), + }; + + let hitsCache = []; + + const getShowMore = helper => () => helper.nextPage().search(); + + return { + getConfiguration: () => ({hitsPerPage}), + init({templatesConfig, helper}) { + this._templateProps = prepareTemplateProps({ + transformData, + defaultTemplates, + templatesConfig, + templates, + }); + + this.showMore = getShowMore(helper); + + infiniteHitsRendering({ + cssClasses, + hits: hitsCache, + results: [], + showMore: this.showMore, + showMoreLabel, + templateProps: this._templateProps, + containerNode, + isLastPage: true, + }, true); + }, + render({results, state}) { + if (state.page === 0) { + hitsCache = []; + } + + hitsCache = [...hitsCache, ...results.hits]; + + const isLastPage = results.nbPages <= results.page + 1; + + infiniteHitsRendering({ + cssClasses, + hits: hitsCache, + results, + showMore: this.showMore, + showMoreLabel, + templateProps: this._templateProps, + containerNode, + isLastPage, + }, false); + }, + }; +}; + +export default connectInfiniteHits; diff --git a/src/widgets/infinite-hits/defaultTemplates.js b/src/connectors/infinite-hits/defaultTemplates.js similarity index 100% rename from src/widgets/infinite-hits/defaultTemplates.js rename to src/connectors/infinite-hits/defaultTemplates.js diff --git a/src/widgets/infinite-hits/__tests__/defaultTemplates-test.js b/src/widgets/infinite-hits/__tests__/defaultTemplates-test.js index e4cf82a0e9..8c0b6e3a92 100644 --- a/src/widgets/infinite-hits/__tests__/defaultTemplates-test.js +++ b/src/widgets/infinite-hits/__tests__/defaultTemplates-test.js @@ -2,7 +2,7 @@ import expect from 'expect'; -import defaultTemplates from '../defaultTemplates'; +import defaultTemplates from '../../../connectors/infinite-hits/defaultTemplates.js'; describe('hits defaultTemplates', () => { it('has a `empty` default template', () => { diff --git a/src/widgets/infinite-hits/__tests__/infinite-hits-test.js b/src/widgets/infinite-hits/__tests__/infinite-hits-test.js index 5d01dd17a9..214e3eba96 100644 --- a/src/widgets/infinite-hits/__tests__/infinite-hits-test.js +++ b/src/widgets/infinite-hits/__tests__/infinite-hits-test.js @@ -11,6 +11,8 @@ import algoliasearchHelper from 'algoliasearch-helper'; import infiniteHits from '../infinite-hits'; import InfiniteHits from '../../../components/InfiniteHits'; +import defaultTemplates from '../../../connectors/infinite-hits/defaultTemplates.js'; + describe('infiniteHits call', () => { it('throws an exception when no container', () => { expect(infiniteHits).toThrow(/^Must provide a container/); @@ -25,10 +27,6 @@ describe('infiniteHits()', () => { let results; let props; let helper; - const defaultTemplates = { - hit: 'hit', - empty: 'empty', - }; beforeEach(() => { helper = algoliasearchHelper({addAlgoliaAgent: () => {}}); @@ -36,14 +34,13 @@ describe('infiniteHits()', () => { ReactDOM = {render: sinon.spy()}; infiniteHits.__Rewire__('ReactDOM', ReactDOM); - infiniteHits.__Rewire__('defaultTemplates', defaultTemplates); container = document.createElement('div'); templateProps = { transformData: undefined, templatesConfig: undefined, templates: defaultTemplates, - useCustomCompileOptions: {hit: false, empty: false}, + useCustomCompileOptions: {item: false, empty: false}, }; widget = infiniteHits({container, cssClasses: {root: ['root', 'cx']}}); widget.init({helper}); diff --git a/src/widgets/infinite-hits/infinite-hits.js b/src/widgets/infinite-hits/infinite-hits.js index a90d5ae600..67bec2e81f 100644 --- a/src/widgets/infinite-hits/infinite-hits.js +++ b/src/widgets/infinite-hits/infinite-hits.js @@ -1,15 +1,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { - bemHelper, - prepareTemplateProps, - getContainerNode, -} from '../../lib/utils.js'; -import cx from 'classnames'; import InfiniteHits from '../../components/InfiniteHits.js'; -import defaultTemplates from './defaultTemplates.js'; -const bem = bemHelper('ais-infinite-hits'); +import connectInfiniteHits from '../../connectors/infinite-hits/connectInfiniteHits.js'; /** * Display the list of results (hits) from the current search @@ -29,75 +22,30 @@ const bem = bemHelper('ais-infinite-hits'); * @param {string|string[]} [options.cssClasses.item] CSS class to add to each result * @return {Object} */ -const usage = ` -Usage: -infiniteHits({ - container, - [ cssClasses.{root,empty,item}={} ], - [ templates.{empty,item} | templates.{empty} ], - [ showMoreLabel="Show more results" ] - [ transformData.{empty,item} | transformData.{empty} ], - [ hitsPerPage=20 ] -})`; -function infiniteHits({ - container, - cssClasses: userCssClasses = {}, - showMoreLabel = 'Show more results', - templates = defaultTemplates, - transformData, - hitsPerPage = 20, - } = {}) { - if (!container) { - throw new Error(`Must provide a container.${usage}`); - } - - const containerNode = getContainerNode(container); - const cssClasses = { - root: cx(bem(null), userCssClasses.root), - item: cx(bem('item'), userCssClasses.item), - empty: cx(bem(null, 'empty'), userCssClasses.empty), - showmore: cx(bem('showmore'), userCssClasses.showmore), - }; - - let hitsCache = []; - - const getShowMore = helper => () => helper.nextPage().search(); - - return { - getConfiguration: () => ({hitsPerPage}), - init({templatesConfig, helper}) { - this._templateProps = prepareTemplateProps({ - transformData, - defaultTemplates, - templatesConfig, - templates, - }); - - this.showMore = getShowMore(helper); - }, - render({results, state}) { - if (state.page === 0) { - hitsCache = []; - } - - hitsCache = [...hitsCache, ...results.hits]; - - const isLastPage = results.nbPages <= results.page + 1; - - ReactDOM.render( - , - containerNode - ); - }, - }; +export default connectInfiniteHits(defaultRendering); + +function defaultRendering({ + cssClasses, + hits, + results, + showMore, + showMoreLabel, + templateProps, + isLastPage, + containerNode, +}, isFirstRendering) { + if (isFirstRendering) return; + + ReactDOM.render( + , + containerNode + ); } - -export default infiniteHits;