Skip to content

Commit

Permalink
Merge pull request #564 from algolia/numeric-selector
Browse files Browse the repository at this point in the history
chore(numericSelector): initial import
  • Loading branch information
bobylito committed Nov 13, 2015
2 parents fc3a8b7 + d0a298c commit 8461715
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 23 deletions.
13 changes: 13 additions & 0 deletions dev/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,17 @@ search.addWidget(
})
);

search.addWidget(
instantsearch.widgets.numericSelector({
container: '#popularity-selector',
attributeName: 'popularity',
options: [
{ label: 'Select a value', value: undefined },
{ label: '1st', value: 1 },
{ label: '2nd', value: 2 },
{ label: '3rd', value: 3 }
]
})
);

search.start();
15 changes: 11 additions & 4 deletions dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,26 +36,33 @@ <h1><a href="./">Instant search demo</a> <small>using instantsearch.js</small></
<input id="search-box" class="form-control" />
</div>
<div class="row">
<div class="col-md-4">
<div class="col-md-3">
<div id="stats"></div>
</div>
<div class="col-md-4 text-right">
<div class="col-md-3 text-right">
<div class="form-inline">
<div class="form-group">
<label for="hits-per-page-select">Show:</label>
<span id="hits-per-page-selector"></span>
</div>
</div>
</div>
<div class="col-md-4 text-right">
<div class="col-md-3 text-right">
<div class="form-inline">
<div class="form-group">
<label for="index-selector-select">Popularity:</label>
<span id="popularity-selector"></span>
</div>
</div>
</div>
<div class="col-md-3 text-right">
<div class="form-inline">
<div class="form-group">
<label for="index-selector-select">Sort by:</label>
<span id="index-selector"></span>
</div>
</div>
</div>

</div>
<div id="hits"></div>
<div id="pagination" class="text-center"></div>
Expand Down
14 changes: 14 additions & 0 deletions docs/_includes/widget-jsdoc/numericSelector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
| Param | Description |
| --- | --- |
| <span class='attr-required'>`options.container`</span><span class="attr-infos">Type: <code>string</code> &#124; <code>DOMElement</code></span> | CSS Selector or DOMElement to insert the widget |
| <span class='attr-required'>`options.attributeName`</span><span class="attr-infos">Type: <code>string</code></span> | Name of the numeric attribute to use |
| <span class='attr-required'>`options.options`</span><span class="attr-infos">Type: <code>Array</code></span> | Array of objects defining the different values and labels |
| <span class='attr-required'>`options.options[i].value`</span><span class="attr-infos">Type: <code>number</code></span> | The numerical value to refine with |
| <span class='attr-required'>`options.options[i].label`</span><span class="attr-infos">Type: <code>string</code></span> | Label to display in the option |
| <span class='attr-optional'>`options.operator`</span><span class="attr-infos">Type: <code>string</code></span> | The operator to use to refine |
| <span class='attr-optional'>`options.cssClasses`</span><span class="attr-infos">Type: <code>Object</code></span> | CSS classes to be added |
| <span class='attr-optional'>`options.cssClasses.root`</span><span class="attr-infos">Type: <code>string</code></span> | CSS classes added to the parent `<select>` |
| <span class='attr-optional'>`options.cssClasses.item`</span><span class="attr-infos">Type: <code>string</code></span> | CSS classes added to each `<option>` |
| <span class='attr-optional'>`options.autoHideContainer`</span><span class="attr-infos">Default:<code class="attr-default">false</code><br />Type: <code>boolean</code></span> | Hide the container when no results match |

<p class="attr-legend">* <span>Required</span></p>
53 changes: 48 additions & 5 deletions docs/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,8 @@ search.addWidget(
{value: 24, label: '24 per page'}
],
cssClasses: {
select: ''
root: '',
item: ''
}
})
);
Expand Down Expand Up @@ -753,13 +754,54 @@ instantsearch.widgets.priceRanges(options);

<div id="price-ranges" class="widget-container"></div>

#### numericSelector

<div class="codebox-combo">

<img class="widget-icon pull-left" src="../img/icon-widget-index.svg">
This filtering widget lets the user choose between numerical refinements from a dropdown menu.
{:.description}

<div class="code-box">
<div class="code-sample-snippet">
{% highlight javascript %}
search.addWidget(
instantsearch.widgets.numericSelector({
container: '#popularity-selector',
attributeName: 'popularity',
operator: '<=',
options: [
{ label: 'Top 1', value: 1 },
{ label: 'Top 10', value: 10 },
{ label: 'Top 100', value: 100 }
],
cssClasses: {
root: '',
item: ''
}
})
);
{% endhighlight %}
</div>
<div class="jsdoc" style='display:none'>
{% highlight javascript %}
instantsearch.widgets.numericSelector(options);
{% endhighlight %}

{% include widget-jsdoc/numericSelector.md %}
</div>
</div>

</div>

<div id="popularity-selector" class="widget-container"></div>

#### clearAll

<div class="codebox-combo">

<img class="widget-icon pull-left" src="../img/icon-widget-clearall.svg">
This filtering widget lets the user choose between ranges of price. Those ranges are dynamically computed based on the returned results.
{:.description}
This widget clears all the refinements that are currently applied.

<div class="code-box">
<div class="code-sample-snippet">
Expand All @@ -776,8 +818,8 @@ search.addWidget(
body: '',
footer: '',
link: '',
},
autoHideContainer: false
},
autoHideContainer: false
})
);
{% endhighlight %}
Expand All @@ -786,6 +828,7 @@ search.addWidget(
{% highlight javascript %}
instantsearch.widgets.clearAll(options);
{% endhighlight %}

{% include widget-jsdoc/clearAll.md %}
</div>
</div>
Expand Down
17 changes: 3 additions & 14 deletions docs/examples/airbnb/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,9 @@
<nav>
<div class="row">
<div class="col-sm-3">Dates</div>
<div class="col-sm-3"><div class="date">10/30/3015</div></div>
<div class="col-sm-3"><div class="date">11/30/3015</div></div>
<div class="col-sm-3">
<div class="guests">
<select>
<option value="1">1 Guest</option>
<option value="2">2 Guests</option>
<option value="3">3 Guests</option>
<option value="4">4 Guests</option>
<option value="5">5 Guests</option>
<option value="6">6 Guests</option>
</select>
</div>
</div>
<div class="col-sm-3"><input class="date form-control" value="10/30/3015" disabled /></div>
<div class="col-sm-3"><input class="date form-control" value="11/30/3015" disabled /></div>
<div class="col-sm-3"><div id="guests"></div></div>
</div>
<div class="row">
<div class="col-sm-3">Room Type</div>
Expand Down
16 changes: 16 additions & 0 deletions docs/examples/airbnb/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,20 @@ search.addWidget(
})
);

search.addWidget(
instantsearch.widgets.numericSelector({
container: '#guests',
attributeName: 'person_capacity',
operator: '>=',
options: [
{ label: '1 guest', value: 1 },
{ label: '2 guests', value: 2 },
{ label: '3 guests', value: 3 },
{ label: '4 guests', value: 4 },
{ label: '5 guests', value: 5 },
{ label: '6 guests', value: 6 }
]
})
);

search.start();
25 changes: 25 additions & 0 deletions docs/examples/airbnb/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

$white: #FFFFFF;
$airbnb-red: #FF585B;
$airbnb-gray: #C4C4C4;
$airbnb-gray-light: #DCE0E0;
$airbnb-gray-lighter: #EDEFED;
$airbnb-text-color: #565A5C;
Expand Down Expand Up @@ -174,6 +175,30 @@ nav {
max-width: 1280px;
}

#guests {
.ais-numeric-selector {
position: relative;
appearance: none;
background: inherit;
width: 100%;
border: 1px solid $airbnb-gray;
height: 34px;
padding: 8px 10px;
&:focus {
outline: none;
}
&:after {
position: absolute;
content: '\25bc';
color: #82888a;
top: 0;
right: 0;
width: 2em;
text-align: center;
}
}
}

#pagination {
text-align: center;
}
Expand Down
1 change: 1 addition & 0 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ instantsearch.widgets = {
menu: require('../widgets/menu/menu.js'),
refinementList: require('../widgets/refinement-list/refinement-list.js'),
numericRefinementList: require('../widgets/numeric-refinement-list/numeric-refinement-list.js'),
numericSelector: require('../widgets/numeric-selector/numeric-selector.js'),
pagination: require('../widgets/pagination/pagination'),
priceRanges: require('../widgets/price-ranges/price-ranges.js'),
searchBox: require('../widgets/search-box/search-box'),
Expand Down
100 changes: 100 additions & 0 deletions widgets/numeric-selector/__tests__/numeric-selector-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* eslint-env mocha */

import React from 'react';
import expect from 'expect';
import sinon from 'sinon';
import jsdom from 'mocha-jsdom';

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

import numericSelector from '../numeric-selector';
import Selector from '../../../components/Selector';

describe('numericSelector()', () => {
jsdom({useEach: true});

let ReactDOM;
let container;
let options;
let cssClasses;
let widget;
let props;
let helper;
let results;
let autoHideContainer;

beforeEach(() => {
autoHideContainer = sinon.stub().returns(Selector);
ReactDOM = {render: sinon.spy()};

numericSelector.__Rewire__('ReactDOM', ReactDOM);
numericSelector.__Rewire__('autoHideContainerHOC', autoHideContainer);

container = document.createElement('div');
options = [
{value: 1, label: 'first'},
{value: 2, label: 'second'}
];
cssClasses = {
root: 'custom-root',
item: 'custom-item'
};
widget = numericSelector({container, options, attributeName: 'aNumAttr', cssClasses});
helper = {
addNumericRefinement: sinon.spy(),
clearRefinements: sinon.spy(),
getRefinements: sinon.stub().returns([]),
search: sinon.spy()
};
results = {
hits: [],
nbHits: 0
};
});

it('doesn\'t configure anything', () => {
expect(widget.getConfiguration).toEqual(undefined);
});

it('calls twice ReactDOM.render(<Selector props />, container)', () => {
widget.render({helper, results, state: helper.state});
widget.render({helper, results, state: helper.state});
props = {
cssClasses: {
root: 'ais-numeric-selector custom-root',
item: 'ais-numeric-selector--item custom-item'
},
currentValue: undefined,
shouldAutoHideContainer: true,
options: [
{value: 1, label: 'first'},
{value: 2, label: 'second'}
],
setValue: () => {}
};
expect(ReactDOM.render.calledTwice).toBe(true, 'ReactDOM.render called twice');
expect(ReactDOM.render.firstCall.args[0]).toEqualJSX(<Selector {...props} />);
expect(ReactDOM.render.firstCall.args[1]).toEqual(container);
expect(ReactDOM.render.secondCall.args[0]).toEqualJSX(<Selector {...props} />);
expect(ReactDOM.render.secondCall.args[1]).toEqual(container);
});

it('sets the underlying numeric refinement', () => {
widget._refine(helper, 2);
expect(helper.addNumericRefinement.calledOnce).toBe(true, 'addNumericRefinement called once');
expect(helper.search.calledOnce).toBe(true, 'search called once');
});

it('cancles the underying numeric refinement', () => {
widget._refine(helper, undefined);
expect(helper.clearRefinements.calledOnce).toBe(true, 'clearRefinements called once');
expect(helper.addNumericRefinement.called).toBe(false, 'addNumericRefinement never called');
expect(helper.search.calledOnce).toBe(true, 'search called once');
});

afterEach(() => {
numericSelector.__ResetDependency__('ReactDOM');
numericSelector.__ResetDependency__('autoHideContainerHOC');
});
});
Loading

0 comments on commit 8461715

Please sign in to comment.