Skip to content

Commit

Permalink
feat(priceRanges): polish priceRanges widget
Browse files Browse the repository at this point in the history
  • Loading branch information
vvo committed Oct 21, 2015
1 parent e5fe344 commit 0994e6f
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 77 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ instantsearch({
[hierarchicalMenu]: ./widgets-screenshots/hierarchicalMenu.png
[menu]: ./widgets-screenshots/menu.png
[rangeSlider]: ./widgets-screenshots/range-slider.png
[priceRanges]: ./widgets-screenshots/price-ranges.png

### searchBox

Expand Down Expand Up @@ -963,6 +964,57 @@ search.addWidget(
);
```

### priceRanges

![Example of the pricesRanges widget][priceRanges]

#### API

```js
/**
* Instantiate a price ranges on a numerical facet
* @param {String|DOMElement} options.container Valid CSS Selector as a string or DOMElement
* @param {String} options.facetName Name of the attribute for faceting
* @param {Object} [options.cssClasses] CSS classes to add to the wrapping elements: root, range
* @param {String|String[]} [options.cssClasses.root] CSS class to add to the root element
* @param {String|String[]} [options.cssClasses.header] CSS class to add to the header element
* @param {String|String[]} [options.cssClasses.body] CSS class to add to the body element
* @param {String|String[]} [options.cssClasses.footer] CSS class to add to the footer element
* @param {String|String[]} [options.cssClasses.range] CSS class to add to the range element
* @param {String|String[]} [options.cssClasses.input] CSS class to add to the min/max input elements
* @param {String|String[]} [options.cssClasses.button] CSS class to add to the button element
* @param {Object} [options.templates] Templates to use for the widget
* @param {String|Function} [options.templates.range] Range template
* @param {Object} [options.labels] Labels to use for the widget
* @param {String|Function} [options.labels.button] Button label
* @param {String|Function} [options.labels.currency] Currency label
* @param {String|Function} [options.labels.to] To label
* @param {boolean} [hideWhenNoResults=true] Hide the container when no results match
* @return {Object}
*/
```

#### Usage

```js
search.addWidget(
instantsearch.widgets.priceRanges({
container: '#price-ranges',
facetName: 'price'
})
);
```

#### Styling

```html

```

```css

```

### hierarchicalMenu

![Example of the hierarchicalMenu widget][hierarchicalMenu]
Expand Down
22 changes: 12 additions & 10 deletions components/PriceRanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ class PriceRange extends React.Component {
this.props.refine(from, to);
}

_handleSubmit(e) {
this.refine(+this.refs.from.value || undefined, +this.refs.to.value || undefined, e);
}

render() {
return (
<div className={this.props.cssClasses.root}>
<div>
{this.props.facetValues.map(facetValue => {
var key = facetValue.from + '_' + facetValue.to;
return (
Expand All @@ -26,7 +30,11 @@ class PriceRange extends React.Component {
</a>
);
})}
<div className={this.props.cssClasses.inputGroup}>
<form
className={this.props.cssClasses.form}
onSubmit={this._handleSubmit.bind(this)}
ref="form"
>
<label>
{this.props.labels.currency}{' '}
<input className={this.props.cssClasses.input} ref="from" type="number" />
Expand All @@ -39,22 +47,16 @@ class PriceRange extends React.Component {
{' '}
<button
className={this.props.cssClasses.button}
onClick={(e) => {
this.refine(+this.refs.from.value || undefined, +this.refs.to.value || undefined, e);
}}
type="submit"
>{this.props.labels.button}</button>
</div>
</form>
</div>
);
}
}

PriceRange.propTypes = {
cssClasses: React.PropTypes.shape({
root: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.arrayOf(React.PropTypes.string)
]),
range: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.arrayOf(React.PropTypes.string)
Expand Down
54 changes: 38 additions & 16 deletions components/__tests__/PriceRanges-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
import React from 'react';
import expect from 'expect';
import TestUtils from 'react-addons-test-utils';
import sinon from 'sinon';
import jsdom from 'mocha-jsdom';

import expectJSX from 'expect-jsx';
expect.extend(expectJSX);

import PriceRanges from '../PriceRanges';
import generateRanges from '../../widgets/price-ranges/generate-ranges.js';

describe('PriceRanges', () => {
var renderer;

jsdom({useEach: true});

beforeEach(() => {
let {createRenderer} = TestUtils;
renderer = createRenderer();
Expand All @@ -17,6 +25,7 @@ describe('PriceRanges', () => {
context('with stats', () => {
var out;
var facetValues;
var props;

beforeEach(() => {
facetValues = generateRanges({
Expand All @@ -26,13 +35,12 @@ describe('PriceRanges', () => {
sum: 2433490.0
});

var props = {
props = {
templateProps: {},
facetValues,
cssClasses: {
root: 'root-class',
range: 'range-class',
inputGroup: 'input-group-class',
form: 'form-class',
button: 'button-class',
input: 'input-class'
},
Expand All @@ -41,18 +49,13 @@ describe('PriceRanges', () => {
to: 'to',
button: 'Go'
},
refine: () => {}
refine: sinon.spy()
};

renderer.render(<PriceRanges {...props} />);
out = renderer.getRenderOutput();
});

it('should add the root class', () => {
expect(out.type).toBe('div');
expect(out.props.className).toEqual('root-class');
});

it('should have the right number of children', () => {
expect(out.props.children.length).toEqual(2);
expect(out.props.children[0].length).toEqual(facetValues.length);
Expand All @@ -64,16 +67,15 @@ describe('PriceRanges', () => {
});
});

it('should have the input group class', () => {
it('should have the form class', () => {
expect(out.props.children.length).toEqual(2);
expect(out.props.children[1].props.className).toEqual('input-group-class');
expect(out.props.children[1].props.className).toEqual('form-class');
});

it('should display the inputs with the associated class & labels', () => {
expect(out.props.children.length).toEqual(2);
var click = out.props.children[1].props.children[6].props.onClick;
expect(out.props.children[1]).toEqual(
<div className="input-group-class">
expect(out.props.children[1]).toEqualJSX(
<form className="form-class" onSubmit={() => {}}>
<label>
USD{' '}<input className="input-class" ref="from" type="number" />
</label>
Expand All @@ -82,9 +84,29 @@ describe('PriceRanges', () => {
USD{' '}<input className="input-class" ref="to" type="number" />
</label>
{' '}
<button className="button-class" onClick={click}>Go</button>
</div>
<button className="button-class" type="submit">Go</button>
</form>
);
});

it('refine on submit', () => {
// cannot currently use shallow rendering to test refs
props.templateProps = {
templates: {header: '', range: '', footer: ''},
templatesConfig: {},
transformData: undefined,
useCustomCompileOptions: {header: false, footer: false, range: false}
};

let eventData = {preventDefault: sinon.spy()};
let component = TestUtils.renderIntoDocument(<PriceRanges {...props} />);
component.refs.from.value = 10;
component.refs.to.value = 20;
TestUtils.Simulate.change(component.refs.from);
TestUtils.Simulate.change(component.refs.to);
TestUtils.Simulate.submit(component.refs.form, eventData);
expect(props.refine.firstCall.args).toEqual([10, 20]);
expect(eventData.preventDefault.calledOnce).toBe(true);
});
});
});
34 changes: 8 additions & 26 deletions example/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,6 @@ search.addWidget(
})
);

search.addWidget(
instantsearch.widgets.refinementList({
container: '#price-range',
facetName: 'price_range',
operator: 'and',
limit: 10,
cssClasses: {
header: 'panel-heading',
root: 'list-group'
},
templates: {
header: 'Price ranges',
item: require('./templates/and.html')
},
transformData: function(data) {
data.name = data.name.replace(/(\d+) - (\d+)/, '$$$1 - $$$2').replace(/> (\d+)/, '> $$$1');
return data;
}
})
);

search.addWidget(
instantsearch.widgets.toggle({
container: '#free-shipping',
Expand Down Expand Up @@ -167,23 +146,26 @@ search.addWidget(
})
);


search.once('render', function() {
document.querySelector('.search').className = 'row search search--visible';
});

search.addWidget(
instantsearch.widgets.priceRanges({
container: '#price_ranges',
container: '#price-ranges',
facetName: 'price',
templates: {
header: 'Price ranges'
},
cssClasses: {
root: 'nav nav-stacked',
header: 'panel-heading',
body: 'nav nav-stacked',
range: 'list-group-item',
inputGroup: 'list-group-item form-inline',
form: 'list-group-item form-inline',
input: 'form-control input-sm fixed-input-sm',
button: 'btn btn-default btn-sm'
},
template: require('./templates/price_range.html')
template: require('./templates/price-ranges.html')
})
);

Expand Down
7 changes: 1 addition & 6 deletions example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,13 @@ <h1>Instant search demo <small>using instantsearch.js</small></h1>

<div class="panel panel-default" id="brands"></div>

<div class="panel panel-default" id="price-range"></div>

<div class="panel panel-default" id="free-shipping"></div>

<div class="panel panel-default" id="price"></div>

<div class="panel panel-default" id="hierarchical-categories"></div>

<div class="panel panel-default">
<div class="panel-heading">Price</div>
<div id="price_ranges" class="list-group"></div>
</div>
<div class="panel panel-default" id="price-ranges"></div>

</div>
<div class="col-md-9">
Expand Down
4 changes: 2 additions & 2 deletions example/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,6 @@ body {
display: block;
}

.fixed-input-sm {
width: 65px !important;
.ais-price-ranges .ais-price-ranges--input{
width: 65px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
${{to}}
{{/to}}
<span>{{count}}</span>
</a>
</a>
Binary file added widgets-screenshots/price-ranges.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0994e6f

Please sign in to comment.