From 1facd9d67149cdd6dfdc443af48e2208180ac4e0 Mon Sep 17 00:00:00 2001 From: vvo Date: Wed, 30 Sep 2015 11:59:18 +0200 Subject: [PATCH] feat: hierarchicalWidget Add hierarchicalWidget. + add limit prop to RefinementList (will then PR other refinement widget to use it) + add `Requirements` section to documentation. We should have it for every widget --- README.md | 57 ++++++++++++ components/RefinementList.js | 24 +++-- example/app.js | 15 ++++ example/index.html | 2 + example/style.css | 4 + index.js | 1 + widgets-screenshots/hierarchicalMenu.png | Bin 0 -> 30083 bytes widgets/hierarchicalMenu.js | 110 +++++++++++++++++++++++ widgets/menu.js | 2 +- 9 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 widgets-screenshots/hierarchicalMenu.png create mode 100644 widgets/hierarchicalMenu.js diff --git a/README.md b/README.md index 63f1eed37e..e36c899578 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ npm run test:watch # developer mode, test only [hits]: ./widgets-screenshots/hits.png [toggle]: ./widgets-screenshots/toggle.png [refinementList]: ./widgets-screenshots/refinement-list.png +[hierarchicalMenu]: ./widgets-screenshots/hierarchicalMenu.png [menu]: ./widgets-screenshots/menu.png [rangeSlider]: ./widgets-screenshots/range-slider.png [urlSync]: ./widgets-screenshots/url-sync.gif @@ -662,6 +663,62 @@ search.addWidget( ); ``` +### hierarchicalMenu + +![Example of the hierarchicalMenu widget][hierarchicalMenu] + +#### API + +```js +/** + * Create a hierarchical menu using multiple attributes + * @param {String|DOMElement} options.container CSS Selector or DOMElement to insert the widget + * @param {String[]} options.attributes Array of attributes to use to generate the hierarchy of the menu. + * You need to follow some conventions: + * @param {String[]} [options.sortBy=['count:desc']] How to sort refinements. Possible values: `count|isRefined|name:asc|desc` + * @param {Number} [options.limit=100] How much facet values to get + * @param {Object} [options.cssClasses] CSS classes to add to the wrapping elements: root, list, item + * @param {String|String[]} [options.cssClasses.root] + * @param {String|String[]} [options.cssClasses.list] + * @param {String|String[]} [options.cssClasses.item] + * @param {Object} [options.templates] Templates to use for the widget + * @param {String|Function} [options.templates.header=''] Header template (root level only) + * @param {String|Function} [options.templates.item='{{name}} {{count}}'] Item template, provided with `name`, `count`, `isRefined`, `path` + * @param {String|Function} [options.templates.footer=''] Footer template (root level only) + * @param {Function} [options.transformData] Method to change the object passed to the item template + * @param {boolean} [hideWhenNoResults=true] Hide the container when there's no results + * @return {Object} + */ +``` + +#### Algolia requirements + +All the `attributes` should be added to `attributesForFaceting` in your index settings. + +Your index's objects must be formatted in a way that is expected by the `hierarchicalMenu` widget: + +```json +{ + "objectID": "123", + "name": "orange", + "categories": { + "lvl0": "fruits", + "lvl1": "fruits > citrus" + } +} +``` + +#### Usage + +```js +search.addWidget( + instantsearch.widgets.hierarchicalMenu({ + container: '#products', + attributes: ['categories.lvl0', 'categories.lvl1', 'categories.lvl2'] + }) +); +``` + ## Browser support We natively support IE10+ and all other modern browsers without any dependency need diff --git a/components/RefinementList.js b/components/RefinementList.js index 83f2b99113..3f5eb896c4 100644 --- a/components/RefinementList.js +++ b/components/RefinementList.js @@ -19,7 +19,10 @@ class RefinementList extends React.Component { // has a checkbox inside, we ignore the first click event because we will get another one. handleClick(value, e) { if (e.target.tagName === 'A' && e.target.href) { + // do not trigger any url change by the href e.preventDefault(); + // do not bubble (so that hierarchical lists are not triggering refine twice) + e.stopPropagation(); } if (e.target.tagName === 'INPUT') { @@ -43,14 +46,21 @@ class RefinementList extends React.Component { render() { return (
- {this.props.facetValues.map(facetValue => { + {this.props.facetValues.slice(0, this.props.limit).map(facetValue => { + var hasChildren = facetValue.data && facetValue.data.length > 0; + + var subList = hasChildren ? + : + null; + return (
+ {subList}
); })} @@ -70,16 +80,20 @@ RefinementList.propTypes = { React.PropTypes.arrayOf(React.PropTypes.string) ]) }), + limit: React.PropTypes.number, facetValues: React.PropTypes.array, Template: React.PropTypes.func, - toggleRefinement: React.PropTypes.func.isRequired + toggleRefinement: React.PropTypes.func.isRequired, + facetNameKey: React.PropTypes.string }; RefinementList.defaultProps = { cssClasses: { item: null, list: null - } + }, + limit: 1000, + facetNameKey: 'name' }; module.exports = RefinementList; diff --git a/example/app.js b/example/app.js index d6ffcfef4a..f8bc82777e 100644 --- a/example/app.js +++ b/example/app.js @@ -139,4 +139,19 @@ search.addWidget( }) ); +search.addWidget( + instantsearch.widgets.hierarchicalMenu({ + container: '#hierarchical-categories', + attributes: ['hierarchicalCategories.lvl0', 'hierarchicalCategories.lvl1', 'hierarchicalCategories.lvl2'], + cssClasses: { + root: 'list-group', + list: 'hierarchical-categories-list' + }, + templates: { + header: '
Hierarchical categories
', + item: require('./templates/category.html') + } + }) +); + search.start(); diff --git a/example/index.html b/example/index.html index 93066ef665..9083dd1e3d 100644 --- a/example/index.html +++ b/example/index.html @@ -35,6 +35,8 @@

Instant search demo using instantsearch.js

+
+
diff --git a/example/style.css b/example/style.css index c48971e993..70cfb248e8 100644 --- a/example/style.css +++ b/example/style.css @@ -44,3 +44,7 @@ body { background-image: linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%); border-color: #ddd; } + +.hierarchical-categories-list .hierarchical-categories-list { + padding-left: 20px; +} diff --git a/index.js b/index.js index 3b55c91566..abe241efad 100644 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ var InstantSearch = require('./lib/InstantSearch'); var instantsearch = toFactory(InstantSearch); instantsearch.widgets = { + hierarchicalMenu: require('./widgets/hierarchicalMenu'), hits: require('./widgets/hits'), indexSelector: require('./widgets/index-selector'), menu: require('./widgets/menu'), diff --git a/widgets-screenshots/hierarchicalMenu.png b/widgets-screenshots/hierarchicalMenu.png new file mode 100644 index 0000000000000000000000000000000000000000..ed5055f510d7f94c800fa70ea849280dbee48c34 GIT binary patch literal 30083 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 C^6)kQ literal 0 HcmV?d00001 diff --git a/widgets/hierarchicalMenu.js b/widgets/hierarchicalMenu.js new file mode 100644 index 0000000000..f83a2c51fb --- /dev/null +++ b/widgets/hierarchicalMenu.js @@ -0,0 +1,110 @@ +var React = require('react'); + +var utils = require('../lib/utils.js'); +var autoHide = require('../decorators/autoHide'); +var bindProps = require('../decorators/bindProps'); +var headerFooter = require('../decorators/headerFooter'); +var RefinementList = autoHide(headerFooter(require('../components/RefinementList'))); +var Template = require('../components/Template'); + +var hierarchicalCounter = 0; +var defaultTemplates = { + header: '', + item: '{{name}} {{count}}', + footer: '' +}; + +/** + * Create a hierarchical menu using multiple attributes + * @param {String|DOMElement} options.container CSS Selector or DOMElement to insert the widget + * @param {String[]} options.attributes Array of attributes to use to generate the hierarchy of the menu. + * You need to follow some conventions: + * @param {String[]} [options.sortBy=['count:desc']] How to sort refinements. Possible values: `count|isRefined|name:asc|desc` + * @param {Number} [options.limit=100] How much facet values to get + * @param {Object} [options.cssClasses] CSS classes to add to the wrapping elements: root, list, item + * @param {String|String[]} [options.cssClasses.root] + * @param {String|String[]} [options.cssClasses.list] + * @param {String|String[]} [options.cssClasses.item] + * @param {Object} [options.templates] Templates to use for the widget + * @param {String|Function} [options.templates.header=''] Header template (root level only) + * @param {String|Function} [options.templates.item='{{name}} {{count}}'] Item template, provided with `name`, `count`, `isRefined`, `path` + * @param {String|Function} [options.templates.footer=''] Footer template (root level only) + * @param {Function} [options.transformData] Method to change the object passed to the item template + * @param {boolean} [hideWhenNoResults=true] Hide the container when there's no results + * @return {Object} + */ +function hierarchicalMenu({ + container = null, + attributes = [], + separator, + limit = 100, + sortBy = ['name:asc'], + cssClasses = { + root: null, + list: null, + item: null + }, + hideWhenNoResults = true, + templates = defaultTemplates, + transformData + }) { + hierarchicalCounter++; + + var containerNode = utils.getContainerNode(container); + var usage = 'Usage: hierarchicalMenu({container, attributes, [separator, sortBy, limit, cssClasses.{root, list, item}, templates.{header, item, footer}, transformData]})'; + + if (!container || !attributes || !attributes.length) { + throw new Error(usage); + } + + var hierarchicalFacetName = 'instantsearch.js-hierarchicalMenu' + hierarchicalCounter; + + return { + getConfiguration: () => ({ + hierarchicalFacets: [{ + name: hierarchicalFacetName, + attributes, + separator + }] + }), + render: function({results, helper, templatesConfig}) { + var facetValues = getFacetValues(results, hierarchicalFacetName, sortBy); + + var templateProps = utils.prepareTemplateProps({ + transformData, + defaultTemplates, + templatesConfig, + templates + }); + + React.render( + 0} + facetNameKey="path" + toggleRefinement={toggleRefinement.bind(null, helper, hierarchicalFacetName)} + />, + containerNode + ); + } + }; +} + +function toggleRefinement(helper, facetName, facetValue) { + helper + .toggleRefinement(facetName, facetValue) + .search(); +} + +function getFacetValues(results, hierarchicalFacetName, sortBy) { + var values = results + .getFacetValues(hierarchicalFacetName, {sortBy: sortBy}); + + return values.data || []; +} + +module.exports = hierarchicalMenu; diff --git a/widgets/menu.js b/widgets/menu.js index 850cd0dbd2..d075603f83 100644 --- a/widgets/menu.js +++ b/widgets/menu.js @@ -55,7 +55,7 @@ function menu({ throw new Error(usage); } - var hierarchicalFacetName = 'instantsearch.js' + hierarchicalCounter; + var hierarchicalFacetName = 'instantsearch.js-menu' + hierarchicalCounter; return { getConfiguration: () => ({