From 187b4bdcd283a6270746a9340e712610bcd9eb52 Mon Sep 17 00:00:00 2001 From: vvo Date: Wed, 23 Sep 2015 17:58:55 +0200 Subject: [PATCH] feat(widgets): auto hide some widgets We now auto hide: - refinementList - menu - rangeSlider - toggle - pagination + option hideWhenNoResults available for all widgets + refactor the way we handle hiding: now somehow a decorator, see: - https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775 - https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775#gistcomment-1574787 For more details, or ask me. --- README.md | 7 +++-- components/IndexSelector.js | 4 ++- components/Pagination/Pagination.js | 8 ++---- components/RefinementList.js | 3 +- components/Slider/index.js | 3 +- components/Stats.js | 3 +- components/Toggle.js | 3 +- decorators/autoHide.js | 43 +++++++++++++++++++++++++++++ index.js | 2 -- lib/style.css | 3 -- widgets/hits.js | 9 +++++- widgets/index-selector.js | 8 ++++-- widgets/menu.js | 22 ++++----------- widgets/pagination.js | 11 +++++++- widgets/range-slider.js | 14 +++++++--- widgets/refinement-list.js | 22 ++++----------- widgets/stats/index.js | 8 +++++- widgets/toggle.js | 8 ++++-- 18 files changed, 121 insertions(+), 60 deletions(-) create mode 100644 decorators/autoHide.js delete mode 100644 lib/style.css diff --git a/README.md b/README.md index 21b74904e6..94cd157b35 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ search.addWidget( * @param {String|DOMElement} options.container Valid CSS Selector as a string or DOMElement * @param {Array} options.indices Array of objects defining the different indices to choose from. Each object must contain a `name` and `label` key. * @param {String} [options.cssClass] Class name(s) to be added to the generated select element + * @param {boolean} [hideIfEmpty=false] Hide the container when no results match * @return {Object} */ ``` @@ -355,6 +356,7 @@ search.addWidget( * @param {String} options.facetName Name of the attribute for faceting (eg. "free_shipping") * @param {String} options.label Human-readable name of the filter (eg. "Free Shipping") * @param {String|Function} [options.template] Item template, provided with `label` and `isRefined` + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ ``` @@ -386,7 +388,7 @@ search.addWidget( * @param {String|Function} [options.templates.footer] Footer template * @param {String|Function} [options.singleRefine=true] Are multiple refinements allowed or only one at the same time. You can use this * to build radio based refinement lists for example - * @param {boolean} [hideWhenNoResults=true] Hide the container when no results match + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ ``` @@ -429,7 +431,7 @@ search.addWidget( * @param {String|Function} [options.templates.header=''] Header template * @param {String|Function} [options.templates.item='{{name}} {{count}}'] Item template, provided with `name`, `count`, `isRefined` * @param {String|Function} [options.templates.footer=''] Footer template - * @param {boolean} [hideWhenNoResults=true] Hide the container when no results match + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ ``` @@ -466,6 +468,7 @@ search.addWidget( * You can also provide * tooltips: {format: function(formattedValue, rawValue) {return '$' + formattedValue}} * So that you can format the tooltip display value as you want + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ ``` diff --git a/components/IndexSelector.js b/components/IndexSelector.js index 7f2c204582..4448ff1fec 100644 --- a/components/IndexSelector.js +++ b/components/IndexSelector.js @@ -1,5 +1,7 @@ var React = require('react'); +var autoHide = require('../decorators/autoHide'); + class IndexSelector extends React.Component { handleChange(event) { this.props.setIndex(event.target.value).search(); @@ -34,4 +36,4 @@ IndexSelector.propTypes = { setIndex: React.PropTypes.func }; -module.exports = IndexSelector; +module.exports = autoHide(IndexSelector); diff --git a/components/Pagination/Pagination.js b/components/Pagination/Pagination.js index 0532c0c0d7..f8a37e8477 100644 --- a/components/Pagination/Pagination.js +++ b/components/Pagination/Pagination.js @@ -6,6 +6,8 @@ var Paginator = require('./Paginator'); var PaginationHiddenLink = require('./PaginationHiddenLink'); var PaginationLink = require('./PaginationLink'); +var autoHide = require('../../decorators/autoHide'); + var bem = require('../../lib/utils').bemHelper; var cx = require('classnames'); @@ -99,10 +101,6 @@ class Pagination extends React.Component { } render() { - if (this.props.nbHits === 0) { - return null; - } - var pager = new Paginator({ currentPage: this.props.currentPage, total: this.props.nbPages, @@ -156,4 +154,4 @@ Pagination.defaultProps = { padding: 3 }; -module.exports = Pagination; +module.exports = autoHide(Pagination); diff --git a/components/RefinementList.js b/components/RefinementList.js index 2b46e09523..1ac96ee58c 100644 --- a/components/RefinementList.js +++ b/components/RefinementList.js @@ -1,6 +1,7 @@ var React = require('react'); var Template = require('./Template'); +var autoHide = require('../decorators/autoHide'); var cx = require('classnames'); class RefinementList extends React.Component { @@ -108,4 +109,4 @@ RefinementList.defaultProps = { } }; -module.exports = RefinementList; +module.exports = autoHide(RefinementList); diff --git a/components/Slider/index.js b/components/Slider/index.js index 47cffa11d4..c4e59aa182 100644 --- a/components/Slider/index.js +++ b/components/Slider/index.js @@ -1,6 +1,7 @@ var React = require('react'); var Nouislider = require('react-nouislider'); +var autoHide = require('../../decorators/autoHide'); require('style?prepend!raw!./index.css'); @@ -38,4 +39,4 @@ Slider.propTypes = { ]) }; -module.exports = Slider; +module.exports = autoHide(Slider); diff --git a/components/Stats.js b/components/Stats.js index 9f911f4885..d50d5f3a2e 100644 --- a/components/Stats.js +++ b/components/Stats.js @@ -1,6 +1,7 @@ var React = require('react'); var Template = require('./Template'); +var autoHide = require('../decorators/autoHide'); class Stats extends React.Component { render() { @@ -42,4 +43,4 @@ Stats.propTypes = { query: React.PropTypes.string }; -module.exports = Stats; +module.exports = autoHide(Stats); diff --git a/components/Toggle.js b/components/Toggle.js index 7111fcb679..e5c7fad4bf 100644 --- a/components/Toggle.js +++ b/components/Toggle.js @@ -1,6 +1,7 @@ var React = require('react'); var Template = require('./Template'); +var autoHide = require('../decorators/autoHide'); var debounce = require('lodash/function/debounce'); class Toggle extends React.Component { @@ -35,4 +36,4 @@ Toggle.propTypes = { isRefined: React.PropTypes.bool }; -module.exports = Toggle; +module.exports = autoHide(Toggle); diff --git a/decorators/autoHide.js b/decorators/autoHide.js new file mode 100644 index 0000000000..fabd203eed --- /dev/null +++ b/decorators/autoHide.js @@ -0,0 +1,43 @@ +var React = require('react'); + +function autoHide(ComposedComponent) { + class AutoHide extends React.Component { + componentDidMount() { + this._hideOrShowContainer(this.props); + } + + componentWillReceiveProps(nextProps) { + this._hideOrShowContainer(nextProps); + } + + _hideOrShowContainer(props) { + var container = React.findDOMNode(this).parentNode; + if (props.hideIfEmpty === true && props.hasResults === false) { + container.style.display = 'none'; + } else if (props.hideIfEmpty === true) { + container.style.display = ''; + } + } + + render() { + if (this.props.hasResults === false && + this.props.hideIfEmpty === true) { + return
; + } + + return ; + } + } + + AutoHide.propTypes = { + hasResults: React.PropTypes.bool.isRequired, + hideIfEmpty: React.PropTypes.bool.isRequired + }; + + // precise displayName for ease of debugging (react dev tool, react warnings) + AutoHide.displayName = ComposedComponent.name + '-AutoHide'; + + return AutoHide; +} + +module.exports = autoHide; diff --git a/index.js b/index.js index 24110d9bf9..3b55c91566 100644 --- a/index.js +++ b/index.js @@ -3,8 +3,6 @@ var toFactory = require('to-factory'); var InstantSearch = require('./lib/InstantSearch'); var instantsearch = toFactory(InstantSearch); -require('style?prepend!raw!./lib/style.css'); - instantsearch.widgets = { hits: require('./widgets/hits'), indexSelector: require('./widgets/index-selector'), diff --git a/lib/style.css b/lib/style.css deleted file mode 100644 index eeb5cd6b90..0000000000 --- a/lib/style.css +++ /dev/null @@ -1,3 +0,0 @@ -.as-display-none { - display: none; -} diff --git a/widgets/hits.js b/widgets/hits.js index e419d7a60b..fa16d06bf0 100644 --- a/widgets/hits.js +++ b/widgets/hits.js @@ -2,7 +2,12 @@ var React = require('react'); var utils = require('../lib/utils.js'); -function hits({container = null, templates = {}, hitsPerPage = 20}) { +function hits({ + container = null, + templates = {}, + hitsPerPage = 20, + hideIfEmpty = false + }) { var Hits = require('../components/Hits'); var containerNode = utils.getContainerNode(container); @@ -16,6 +21,8 @@ function hits({container = null, templates = {}, hitsPerPage = 20}) { results={results} helper={helper} noResultsTemplate={templates.empty} + hideIfEmpty={hideIfEmpty} + hasResults={results.hits.length > 0} hitTemplate={templates.hit} />, containerNode diff --git a/widgets/index-selector.js b/widgets/index-selector.js index 021d28a013..7ef7b1f853 100644 --- a/widgets/index-selector.js +++ b/widgets/index-selector.js @@ -8,12 +8,14 @@ var utils = require('../lib/utils.js'); * @param {String|DOMElement} options.container Valid CSS Selector as a string or DOMElement * @param {Array} options.indices Array of objects defining the different indices to choose from. Each object must contain a `name` and `label` key. * @param {String} [options.cssClass] Class name(s) to be added to the generated select element + * @param {boolean} [hideIfEmpty=false] Hide the container when no results match * @return {Object} */ function indexSelector({ container = null, indices = null, - cssClass + cssClass, + hideIfEmpty = false }) { var IndexSelector = require('../components/IndexSelector'); var containerNode = utils.getContainerNode(container); @@ -32,7 +34,7 @@ function indexSelector({ } }, - render: function({helper}) { + render: function({helper, results}) { var containerId = containerNode.id; React.render( 0} setIndex={helper.setIndex.bind(helper)} />, containerNode diff --git a/widgets/menu.js b/widgets/menu.js index d76c5c5082..39da2b06a3 100644 --- a/widgets/menu.js +++ b/widgets/menu.js @@ -28,7 +28,7 @@ var defaults = require('lodash/object/defaults'); * @param {String|Function} [options.templates.item='{{name}} {{count}}'] Item template, provided with `name`, `count`, `isRefined` * @param {String|Function} [options.templates.footer=''] Footer template * @param {Function} [options.transformData] Method to change the object passed to the item template - * @param {boolean} [hideWhenNoResults=true] Hide the container when no results match + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ function menu({ @@ -41,7 +41,7 @@ function menu({ list: null, item: null }, - hideWhenNoResults = true, + hideIfEmpty = true, templates = defaultTemplates, transformData = null }) { @@ -70,26 +70,16 @@ function menu({ }] }), render: function({results, helper}) { - var values = getFacetValues(results, hierarchicalFacetName, sortBy, limit); - - if (values.length === 0) { - React.render(
, containerNode); - if (hideWhenNoResults === true) { - containerNode.classList.add('as-display-none'); - } - return; - } - - if (hideWhenNoResults === true) { - containerNode.classList.remove('as-display-none'); - } + var facetValues = getFacetValues(results, hierarchicalFacetName, sortBy, limit); React.render( 0} toggleRefinement={toggleRefinement.bind(null, helper, hierarchicalFacetName)} />, containerNode diff --git a/widgets/pagination.js b/widgets/pagination.js index bc32e1adc1..cb024542bd 100644 --- a/widgets/pagination.js +++ b/widgets/pagination.js @@ -2,7 +2,14 @@ var React = require('react'); var utils = require('../lib/utils.js'); -function pagination({container, cssClass, labels, maxPages, showFirstLast} = {}) { +function pagination({ + container = null, + cssClass, + labels, + maxPages, + showFirstLast, + hideIfEmpty = true + }) { var Pagination = require('../components/Pagination/Pagination.js'); var containerNode = utils.getContainerNode(container); @@ -21,6 +28,8 @@ function pagination({container, cssClass, labels, maxPages, showFirstLast} = {}) nbPages={nbPages} setCurrentPage={helper.setCurrentPage.bind(helper)} cssClass={cssClass} + hideIfEmpty={hideIfEmpty} + hasResults={results.hits.length > 0} labels={labels} showFirstLast={showFirstLast} />, diff --git a/widgets/range-slider.js b/widgets/range-slider.js index 788e1ced1d..68c9fc0256 100644 --- a/widgets/range-slider.js +++ b/widgets/range-slider.js @@ -11,12 +11,14 @@ var utils = require('../lib/utils.js'); * You can also provide * tooltips: {format: function(formattedValue, rawValue) {return '$' + formattedValue}} * So that you can format the tooltip display value as you want + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ function rangeSlider({ container = null, facetName = null, - tooltips = true + tooltips = true, + hideIfEmpty = true }) { var Slider = require('../components/Slider'); @@ -58,15 +60,19 @@ function rangeSlider({ var currentRefinement = this._getCurrentRefinement(helper); - if (!stats) { - React.render(
, containerNode); - return; + if (stats === undefined) { + stats = { + min: null, + max: null + }; } React.render( , diff --git a/widgets/refinement-list.js b/widgets/refinement-list.js index b6eaca104c..24f59a390d 100644 --- a/widgets/refinement-list.js +++ b/widgets/refinement-list.js @@ -32,7 +32,7 @@ var defaults = require('lodash/object/defaults'); * @param {Function} [options.transformData] Method to change the object passed to the item template * @param {String|Function} [options.singleRefine=true] Are multiple refinements allowed or only one at the same time. You can use this * to build radio based refinement lists for example - * @param {boolean} [hideWhenNoResults=true] Hide the container when no results match + * @param {boolean} [hideIfEmpty=true] Hide the container when no results match * @return {Object} */ function refinementList({ @@ -46,7 +46,7 @@ function refinementList({ list: null, item: null }, - hideWhenNoResults = true, + hideIfEmpty = true, templates = defaultTemplates, transformData = null, singleRefine = false @@ -79,26 +79,16 @@ function refinementList({ [operator === 'and' ? 'facets' : 'disjunctiveFacets']: [facetName] }), render: function({results, helper}) { - var values = results.getFacetValues(facetName, {sortBy: sortBy}).slice(0, limit); - - if (values.length === 0) { - React.render(
, containerNode); - if (hideWhenNoResults === true) { - containerNode.classList.add('as-display-none'); - } - return; - } - - if (hideWhenNoResults === true) { - containerNode.classList.remove('as-display-none'); - } + var facetValues = results.getFacetValues(facetName, {sortBy: sortBy}).slice(0, limit); React.render( 0} toggleRefinement={toggleRefinement.bind(null, helper, singleRefine, facetName)} />, containerNode diff --git a/widgets/stats/index.js b/widgets/stats/index.js index 2b29f9bfe1..27f1a7019a 100644 --- a/widgets/stats/index.js +++ b/widgets/stats/index.js @@ -3,7 +3,11 @@ var React = require('react'); var utils = require('../../lib/utils.js'); var defaultTemplate = require('./template.html'); -function stats({container = null, template = defaultTemplate}) { +function stats({ + container = null, + template = defaultTemplate, + hideIfEmpty = true + }) { var Stats = require('../../components/Stats'); var containerNode = utils.getContainerNode(container); @@ -15,6 +19,8 @@ function stats({container = null, template = defaultTemplate}) { render: function({results, templateHelpers}) { React.render( 0} + hideIfEmpty={hideIfEmpty} hitsPerPage={results.hitsPerPage} nbHits={results.nbHits} nbPages={results.nbPages} diff --git a/widgets/toggle.js b/widgets/toggle.js index 40c01e7f0e..2e2a366675 100644 --- a/widgets/toggle.js +++ b/widgets/toggle.js @@ -11,13 +11,15 @@ var defaultTemplate = '