Skip to content

Commit

Permalink
feat(connector): connectInfiniteHits (iteration 2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexandre Stanislawski committed Mar 21, 2017
1 parent bca09af commit 410459c
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 111 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


import sinon from 'sinon';

import jsHelper from 'algoliasearch-helper';
Expand Down Expand Up @@ -42,9 +40,6 @@ describe('connectInfiniteHits', () => {
// test if isFirstRendering is true during init
expect(rendering.lastCall.args[1]).toBe(true);

const firstRenderingOptions = rendering.lastCall.args[0];
expect(firstRenderingOptions.containerNode).toBe(container);

widget.render({
results: new SearchResults(helper.state, [{
hits: [],
Expand All @@ -57,9 +52,6 @@ describe('connectInfiniteHits', () => {
// test that rendering has been called during init with isFirstRendering = false
expect(rendering.callCount).toBe(2);
expect(rendering.lastCall.args[1]).toBe(false);

const secondRenderingOptions = rendering.lastCall.args[0];
expect(secondRenderingOptions.containerNode).toBe(container);
});

it('Provides the hits and the whole results', () => {
Expand Down
72 changes: 4 additions & 68 deletions src/connectors/infinite-hits/connectInfiniteHits.js
Original file line number Diff line number Diff line change
@@ -1,87 +1,25 @@
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,
});

init({instantSearchInstance, helper}) {
this.showMore = getShowMore(helper);

infiniteHitsRendering({
cssClasses,
hits: hitsCache,
results: undefined,
showMore: this.showMore,
showMoreLabel,
templateProps: this._templateProps,
containerNode,
isLastPage: true,
instantSearchInstance,
}, true);
},
render({results, state}) {
render({results, state, instantSearchInstance}) {
if (state.page === 0) {
hitsCache = [];
}
Expand All @@ -91,14 +29,12 @@ const connectInfiniteHits = infiniteHitsRendering => ({
const isLastPage = results.nbPages <= results.page + 1;

infiniteHitsRendering({
cssClasses,
hits: hitsCache,
results,
showMore: this.showMore,
showMoreLabel,
templateProps: this._templateProps,
containerNode,
isLastPage,
instantSearchInstance,
}, false);
},
};
Expand Down
5 changes: 1 addition & 4 deletions src/widgets/hits/__tests__/defaultTemplates-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@



import defaultTemplates from '../../../connectors/hits/defaultTemplates.js';
import defaultTemplates from '../defaultTemplates.js';

describe('hits defaultTemplates', () => {
it('has a `empty` default template', () => {
Expand Down
5 changes: 1 addition & 4 deletions src/widgets/infinite-hits/__tests__/defaultTemplates-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@



import defaultTemplates from '../../../connectors/infinite-hits/defaultTemplates.js';
import defaultTemplates from '../defaultTemplates.js';

describe('hits defaultTemplates', () => {
it('has a `empty` default template', () => {
Expand Down
6 changes: 3 additions & 3 deletions src/widgets/infinite-hits/__tests__/infinite-hits-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ expect.extend(expectJSX);
import algoliasearchHelper from 'algoliasearch-helper';
import infiniteHits from '../infinite-hits';
import InfiniteHits from '../../../components/InfiniteHits';
import defaultTemplates from '../../../connectors/infinite-hits/defaultTemplates.js';
import defaultTemplates from '../defaultTemplates.js';

describe('infiniteHits call', () => {
it('throws an exception when no container', () => {
expect(infiniteHits).toThrow(/^Must provide a container/);
expect(infiniteHits).toThrow();
});
});

Expand Down Expand Up @@ -38,7 +38,7 @@ describe('infiniteHits()', () => {
useCustomCompileOptions: {item: false, empty: false},
};
widget = infiniteHits({container, cssClasses: {root: ['root', 'cx']}});
widget.init({helper});
widget.init({helper, instantSearchInstance: {}});
results = {hits: [{first: 'hit', second: 'hit'}]};
});

Expand Down
113 changes: 89 additions & 24 deletions src/widgets/infinite-hits/infinite-hits.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,56 @@ import React from 'react';
import ReactDOM from 'react-dom';
import InfiniteHits from '../../components/InfiniteHits.js';

import {
bemHelper,
prepareTemplateProps,
getContainerNode,
} from '../../lib/utils.js';
import cx from 'classnames';
import defaultTemplates from './defaultTemplates.js';

import connectInfiniteHits from '../../connectors/infinite-hits/connectInfiniteHits.js';

const bem = bemHelper('ais-infinite-hits');

const renderer = ({
cssClasses,
containerNode,
renderState,
templates,
transformData,
}) => ({
hits,
results,
showMore,
showMoreLabel,
isLastPage,
instantSearchInstance,
}, isFirstRendering) => {
if (isFirstRendering) {
renderState.templateProps = prepareTemplateProps({
transformData,
defaultTemplates,
templatesConfig: instantSearchInstance.templatesConfig,
templates,
});
return;
}

ReactDOM.render(
<InfiniteHits
cssClasses={cssClasses}
hits={hits}
results={results}
showMore={showMore}
showMoreLabel={showMoreLabel}
templateProps={renderState.templateProps}
isLastPage={isLastPage}
/>,
containerNode
);
};

/**
* Display the list of results (hits) from the current search
* @function infiniteHits
Expand All @@ -22,30 +70,47 @@ import connectInfiniteHits from '../../connectors/infinite-hits/connectInfiniteH
* @param {string|string[]} [options.cssClasses.item] CSS class to add to each result
* @return {Object}
*/
export default connectInfiniteHits(defaultRendering);
const usage = `
Usage:
infiniteHits({
container,
[ cssClasses.{root,empty,item}={} ],
[ templates.{empty,item} | templates.{empty} ],
[ transformData.{empty,item} | transformData.{empty} ],
[ hitsPerPage=20 ]
})`;

function defaultRendering({
cssClasses,
hits,
results,
showMore,
showMoreLabel,
templateProps,
isLastPage,
containerNode,
}, isFirstRendering) {
if (isFirstRendering) return;
export default function infiniteHits({
container,
cssClasses: userCssClasses = {},
showMoreLabel = 'Show more results',
templates = defaultTemplates,
transformData,
hitsPerPage = 20,
}) {
if (!container) {
throw new Error(`Must provide a container.${usage}`);
}

ReactDOM.render(
<InfiniteHits
cssClasses={cssClasses}
hits={hits}
results={results}
showMore={showMore}
showMoreLabel={showMoreLabel}
templateProps={templateProps}
isLastPage={isLastPage}
/>,
containerNode
);
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),
};

const specializedRenderer = renderer({
containerNode,
cssClasses,
transformData,
templates,
renderState: {},
});

const makeInfiniteHits = connectInfiniteHits(specializedRenderer);
return makeInfiniteHits({
showMoreLabel,
hitsPerPage,
});
}

0 comments on commit 410459c

Please sign in to comment.