Skip to content

Commit

Permalink
feat(hits): adds allItems template as an alternative to item
Browse files Browse the repository at this point in the history
This way the user can specify how to do the complete rendering of
the hits.
  • Loading branch information
Alexandre Stanislawski committed Jan 25, 2016
1 parent 0eca736 commit 1f3f889
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 9 deletions.
13 changes: 12 additions & 1 deletion dev/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,25 @@ search.addWidget(
})
);

search.addWidget(
instantsearch.widgets.hits({
container: '#hits-table',
templates: {
empty: require('./templates/no-results.html'),
allItems: require('./templates/all-items.html')
},
hitsPerPage: 24
})
);

search.addWidget(
instantsearch.widgets.hits({
container: '#hits',
templates: {
empty: require('./templates/no-results.html'),
item: require('./templates/item.html')
},
hitsPerPage: 6
hitsPerPage: 24
})
);

Expand Down
3 changes: 3 additions & 0 deletions dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ <h1><a href="./">Instant search demo</a> <small>using instantsearch.js</small></
</div>
</div>
</div>
<h3>Results overview</h3>
<div id="hits-table"></div>
<h3>Results</h3>
<div id="hits"></div>
<div id="pagination" class="text-center"></div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions dev/templates/all-items.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<table class="table table-striped table-condensed">
<thead>
<tr><th>name</th><th>brand</th><th>price</th></tr>
</thead>
<tbody>
{{#hits}}
<tr><td><a href="#hit-{{objectID}}">{{name}}</a></td><td>{{brand}}</td><td>{{price}}</td></tr>
{{/hits}}
</tbody>
</table>
3 changes: 2 additions & 1 deletion dev/templates/item.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="hit">
<div class="hit" id="hit-{{objectID}}">
<div class="media">
<a class="pull-left" href="{{url}}">
<img class="media-object" src="{{image}}">
Expand All @@ -10,4 +10,5 @@ <h4>{{{_highlightResult.name.value}}}</h4>
{{#free_shipping}}<span class="badge pull-right">Free Shipping</span>{{/free_shipping}}
</div>
</div>
<a href="#">Go back to top</a>
</div>
19 changes: 19 additions & 0 deletions src/components/Hits.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ class Hits extends React.Component {
return <div className={this.props.cssClasses.root}>{renderedHits}</div>;
}

renderAllResults() {
return (
<Template
cssClass={this.props.cssClasses.allItems}
data={this.props.results}
templateKey="allItems"
{...this.props.templateProps}
/>
);
}

renderNoResults() {
let className = this.props.cssClasses.root + ' ' + this.props.cssClasses.empty;
return (
Expand All @@ -34,6 +45,13 @@ class Hits extends React.Component {

render() {
if (this.props.results.hits.length > 0) {
const useAllItemsTemplate =
this.props.templateProps &&
this.props.templateProps.templates &&
this.props.templateProps.templates.allItems;
if (useAllItemsTemplate) {
return this.renderAllResults();
}
return this.renderWithResults();
}
return this.renderNoResults();
Expand All @@ -44,6 +62,7 @@ Hits.propTypes = {
cssClasses: React.PropTypes.shape({
root: React.PropTypes.string,
item: React.PropTypes.string,
allItems: React.PropTypes.string,
empty: React.PropTypes.string
}),
results: React.PropTypes.object,
Expand Down
38 changes: 37 additions & 1 deletion src/components/__tests__/Hits-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('Hits', () => {
templateProps = {};
});

it('render hits when present', () => {
it('render hits when present (item template)', () => {
results = {hits: [{
objectID: 'hello'
}, {
Expand Down Expand Up @@ -58,6 +58,42 @@ describe('Hits', () => {
);
});

it('render hits when present (allItems template)', () => {
results = {hits: [{
objectID: 'hello'
}, {
objectID: 'mom'
}]};

const templateProps2 = {
...templateProps,
templates: {
allItems: 'all items'
}
};

let props = {
results,
templateProps: templateProps2,
cssClasses: {
root: 'custom-root',
allItems: 'custom-item',
empty: 'custom-empty'
}
};
renderer.render(<Hits {...props} />);
let out = renderer.getRenderOutput();

expect(out).toEqualJSX(
<Template
cssClass="custom-item"
data={results}
templateKey="allItems"
{...templateProps2}
/>
);
});

it('renders a specific template when no results', () => {
results = {hits: []};

Expand Down
6 changes: 5 additions & 1 deletion src/widgets/hits/__tests__/hits-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('hits call', () => {
jsdom({useEach: true});

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

Expand Down Expand Up @@ -65,6 +65,10 @@ describe('hits()', () => {
expect(ReactDOM.render.secondCall.args[1]).toEqual(container);
});

it('does not accept both item and allItems templates', () => {
expect(hits.bind({container, templates: {item: '', allItems: ''}})).toThrow();
});

afterEach(() => {
hits.__ResetDependency__('ReactDOM');
hits.__ResetDependency__('defaultTemplates');
Expand Down
17 changes: 12 additions & 5 deletions src/widgets/hits/hits.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,26 @@ import defaultTemplates from './defaultTemplates.js';
* @param {string|DOMElement} options.container CSS Selector or DOMElement to insert the widget
* @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.
* @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.allItems=''] Template to use for each result. (can't be used with item template). This template will receive a complete SearchResults result object, this object contains the key hits that contains all the records retrieved.
* @param {Object} [options.transformData] Method to change the object passed to the templates
* @param {Function} [options.transformData.empty=identity] Method used to change the object passed to the empty template
* @param {Function} [options.transformData.item=identity] Method used to change the object passed to the item template
* @param {Function} [options.transformData.allItems=identity] Method used to change the object passed to the item template
* @param {number} [hitsPerPage=20] The number of hits to display per page
* @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.cssClasses.item] CSS class to add to each result
* @return {Object}
*/
const usage = `Usage:
const usage = `
Usage:
hits({
container,
[ cssClasses.{root,empty,item}={} ],
[ templates.{empty,item} ],
[ transformData.{empty=identity,item=identity} ],
[ templates.{empty,item} | templates.{empty, allItems} ],
[ transformData.{empty=identity,item=identity} | transformData.{empty, allItems} ],
[ hitsPerPage=20 ]
})`;
function hits({
Expand All @@ -41,7 +44,11 @@ function hits({
hitsPerPage = 20
} = {}) {
if (!container) {
throw new Error(usage);
throw new Error('Must provide a container.' + usage);
}

if (templates.item && templates.allItems) {
throw new Error('Must contain only allItems OR item template.' + usage);
}

let containerNode = utils.getContainerNode(container);
Expand Down

0 comments on commit 1f3f889

Please sign in to comment.