Skip to content

Commit 5916e4f

Browse files
committed
Merge pull request #86 from algolia/feat/slider
FIX #11 feat: Slider widget
2 parents cd25b7f + bc5f4a8 commit 5916e4f

22 files changed

+330
-38
lines changed

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ API is unstable. We welcome any idea.
2020
- [toggle](#toggle)
2121
- [refinementList](#refinementlist)
2222
- [menu](#menu)
23+
- [rangeSlider](#rangeslider)
2324

2425
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
2526

@@ -99,6 +100,7 @@ npm run test:coverage
99100
[toggle]: ./widgets-screenshots/toggle.png
100101
[refinementList]: ./widgets-screenshots/refinement-list.png
101102
[menu]: ./widgets-screenshots/menu.png
103+
[rangeSlider]: ./widgets-screenshots/range-slider.png
102104

103105
### searchBox
104106

@@ -344,3 +346,43 @@ search.addWidget(
344346
})
345347
);
346348
```
349+
350+
### rangeSlider
351+
352+
![Example of the rangeSlider widget][rangeSlider]
353+
354+
#### API
355+
356+
```js
357+
/**
358+
* Instantiate a slider based on a numeric attribute
359+
* @param {String|DOMElement} options.container Valid CSS Selector as a string or DOMElement
360+
* @param {String} options.facetName Name of the attribute for faceting
361+
* @param {Boolean|Object} [options.tooltips=true] Should we show tooltips or not.
362+
* The default tooltip will show the formatted corresponding value without any other token.
363+
* You can also provide
364+
* tooltips: {format: function(formattedValue, rawValue) {return '$' + formattedValue}}
365+
* So that you can format the tooltip display value as you want
366+
* @return {Object}
367+
*/
368+
```
369+
370+
#### Usage
371+
372+
```html
373+
<div id="price"></div>
374+
```
375+
376+
```js
377+
search.addWidget(
378+
instantsearch.widgets.rangeSlider({
379+
container: '#price',
380+
facetName: 'price',
381+
tooltips: {
382+
format: function(formattedValue) {
383+
return '$' + formattedValue;
384+
}
385+
}
386+
})
387+
);
388+
```

components/Slider/index.css

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
.as-range-slider--target,
2+
.as-range-slider--target * {
3+
-webkit-touch-callout: none;
4+
-webkit-user-select: none;
5+
-ms-touch-action: none;
6+
-ms-user-select: none;
7+
-moz-user-select: none;
8+
-moz-box-sizing: border-box;
9+
box-sizing: border-box;
10+
}
11+
.as-range-slider--target {
12+
position: relative;
13+
direction: ltr;
14+
padding: 0 15px;
15+
border-radius: 5px;
16+
background: #f3f4f7;
17+
height: 18px;
18+
}
19+
.as-range-slider--base {
20+
width: 100%;
21+
height: 100%;
22+
position: relative;
23+
z-index: 1; /* Fix 401 */
24+
}
25+
.as-range-slider--origin {
26+
position: absolute;
27+
right: 0;
28+
top: 0;
29+
left: 0;
30+
bottom: 0;
31+
}
32+
.as-range-slider--state-tap .as-range-slider--origin {
33+
-webkit-transition: left 0.3s, top 0.3s;
34+
transition: left 0.3s, top 0.3s;
35+
}
36+
.as-range-slider--state-drag * {
37+
cursor: inherit !important;
38+
}
39+
40+
/* Painting and performance;
41+
* Browsers can paint handles in their own layer.
42+
*/
43+
.as-range-slider--base {
44+
-webkit-transform: translate3d(0,0,0);
45+
transform: translate3d(0,0,0);
46+
}
47+
48+
.as-range-slider--connect {
49+
background: #46aeda;
50+
-webkit-transition: background 450ms;
51+
transition: background 450ms;
52+
}
53+
54+
.as-range-slider--background {
55+
background: #f3f4f7;
56+
}
57+
58+
.as-range-slider--handle {
59+
width: 1px;
60+
height: 28px;
61+
position: relative;
62+
z-index: 1;
63+
background: #8191b1;
64+
cursor: pointer;
65+
}
66+
67+
.as-range-slider--handle-lower {
68+
bottom: 10px;
69+
}
70+
71+
.as-range-slider--active {
72+
background: #1f3b5d;
73+
}
74+
75+
.as-range-slider--tooltip {
76+
position: absolute;
77+
color: #1f3b5d;
78+
border: 1px solid #8191b1;
79+
background: #ffffff;
80+
padding: 2px 3px;
81+
}
82+
83+
.as-range-slider--handle:hover {
84+
background: #1f3b5d;
85+
}
86+
87+
.as-range-slider--active .as-range-slider--tooltip, .as-range-slider--handle:hover .as-range-slider--tooltip {
88+
background: #46aeda;
89+
color: #ffffff;
90+
border-color: #1f3b5d;
91+
}
92+
93+
.as-range-slider--handle-lower .as-range-slider--tooltip {
94+
border-radius: 5px 5px 5px 0;
95+
top: -24px;
96+
}
97+
98+
.as-range-slider--handle-upper .as-range-slider--tooltip {
99+
bottom: -24px;
100+
border-radius: 5px 0 5px 5px;
101+
right: 0;
102+
}

components/Slider/index.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
var React = require('react');
2+
3+
var Nouislider = require('react-nouislider');
4+
5+
require('style?prepend!raw!./index.css');
6+
7+
var cssPrefix = 'as-range-slider--';
8+
9+
class Slider extends React.Component {
10+
11+
// we are only interested in rawValues
12+
handleChange(formattedValues, handleId, rawValues) {
13+
this.props.onChange(rawValues);
14+
}
15+
16+
render() {
17+
return (
18+
<Nouislider
19+
{...this.props}
20+
onChange={this.handleChange.bind(this)}
21+
animate={false}
22+
behaviour={'snap'}
23+
connect
24+
cssPrefix={cssPrefix}
25+
/>
26+
);
27+
}
28+
}
29+
30+
Slider.propTypes = {
31+
onSlide: React.PropTypes.func,
32+
onChange: React.PropTypes.func,
33+
range: React.PropTypes.object.isRequired,
34+
start: React.PropTypes.arrayOf(React.PropTypes.number).isRequired,
35+
tooltips: React.PropTypes.oneOfType([
36+
React.PropTypes.bool,
37+
React.PropTypes.object
38+
])
39+
};
40+
41+
module.exports = Slider;

components/Template.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
var hogan = require('hogan.js');
21
var React = require('react');
32

3+
var renderTemplate = require('../lib/utils').renderTemplate;
4+
45
class Template {
56
render() {
6-
var content;
7-
8-
if (typeof this.props.template === 'string') {
9-
content = hogan.compile(this.props.template).render(this.props.data);
10-
} else {
11-
content = this.props.template(this.props.data);
12-
}
7+
var content = renderTemplate(this.props.template, this.props.data);
138

149
return <div dangerouslySetInnerHTML={{__html: content}} />;
1510
}

example/app.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
require('./style.css');
2-
31
var instantsearch = require('../');
42

53
var search = instantsearch({
@@ -93,4 +91,16 @@ search.addWidget(
9391
})
9492
);
9593

94+
search.addWidget(
95+
instantsearch.widgets.rangeSlider({
96+
container: '#price',
97+
facetName: 'price',
98+
tooltips: {
99+
format: function(formattedValue) {
100+
return '$' + formattedValue;
101+
}
102+
}
103+
})
104+
);
105+
96106
search.start();

example/index.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<title>Instant search demo built with instantsearch.js</title>
66
<link rel="stylesheet" href="//cdn.jsdelivr.net/bootstrap/3.3.5/css/bootstrap.min.css">
77
<link rel="stylesheet" href="//cdn.jsdelivr.net/bootstrap/3.3.5/css/bootstrap-theme.min.css">
8+
<link rel="stylesheet" href="./style.css">
89
</head>
910
<body>
1011
<div class="container">
@@ -34,14 +35,21 @@ <h1>Instant search demo <small>using instantsearch.js</small></h1>
3435
</div>
3536

3637
<div class="panel panel-default">
37-
<div class="panel-heading">Toggle filters</div>
38+
<div class="panel-heading">Shipping</div>
3839
<div class="panel-body">
3940
<ul class="nav nav-stacked">
4041
<li><div id="free_shipping"></div></li>
4142
</ul>
4243
</div>
4344
</div>
4445

46+
<div class="panel panel-default">
47+
<div class="panel-heading">Price</div>
48+
<div class="panel-body">
49+
<div id="price"></div>
50+
</div>
51+
</div>
52+
4553
</div>
4654
<div class="col-md-9">
4755
<div class="row">

example/style.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ body {
3535
.hit {
3636
height: 125px;
3737
}
38+
39+
#price {
40+
padding: 30px 0;
41+
}

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ instantsearch.widgets = {
1010
refinementList: require('./widgets/refinement-list'),
1111
pagination: require('./widgets/pagination'),
1212
searchBox: require('./widgets/search-box'),
13+
rangeSlider: require('./widgets/range-slider'),
1314
stats: require('./widgets/stats'),
1415
toggle: require('./widgets/toggle')
1516
};

lib/utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
var isString = require('lodash/lang/isString');
2+
3+
function getContainerNode(value) {
4+
if (isString(value)) {
5+
return document.querySelector(value);
6+
}
7+
return value;
8+
}
9+
10+
function renderTemplate(template, data) {
11+
var hogan = require('hogan.js');
12+
var content;
13+
14+
if (typeof template === 'string') {
15+
content = hogan.compile(template).render(data);
16+
} else if (typeof template === 'function') {
17+
content = template(data);
18+
} else {
19+
throw new Error('Template must be `string` or `function`');
20+
}
21+
22+
return content;
23+
}
24+
25+
module.exports = {
26+
getContainerNode: getContainerNode,
27+
renderTemplate: renderTemplate
28+
};

lib/widget-utils.js

Lines changed: 0 additions & 12 deletions
This file was deleted.

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@
2121
"browser": {
2222
"lodash": "lodash-compat"
2323
},
24+
"repository": "algolia/intantsearch.js",
2425
"devDependencies": {
2526
"babel": "5.8.23",
2627
"babel-eslint": "4.1.1",
2728
"babel-loader": "5.3.2",
2829
"babel-tape-runner": "1.2.0",
29-
"css-loader": "0.16.0",
3030
"doctoc": "0.15.0",
3131
"eslint": "1.4.1",
3232
"eslint-config-airbnb": "0.0.8",
@@ -37,8 +37,9 @@
3737
"onchange": "2.0.0",
3838
"pretty-bytes": "2.0.1",
3939
"proxyquire": "1.7.1",
40+
"raw-loader": "0.5.1",
4041
"sinon": "1.16.1",
41-
"style-loader": "0.12.3",
42+
"style-loader": "algolia/style-loader#3ec5a83b23607339e25fe65d51ae823ba9a8e7df",
4243
"tap-spec": "4.1.0",
4344
"tape": "4.2.0",
4445
"uglifyjs": "2.4.10",
@@ -47,12 +48,12 @@
4748
},
4849
"dependencies": {
4950
"algoliasearch": "3.7.8",
50-
"algoliasearch-helper": "2.3.0",
51+
"algoliasearch-helper": "2.3.5",
5152
"classnames": "2.1.3",
5253
"hogan.js": "3.0.2",
5354
"lodash": "3.10.1",
54-
"raw-loader": "0.5.1",
5555
"react": "0.13.3",
56+
"react-nouislider": "1.2.0",
5657
"to-factory": "1.0.0"
5758
},
5859
"license": "MIT"

webpack.example.config.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ module.exports = {
1010
test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'
1111
}, {
1212
test: /\.html$/, exclude: /node_modules/, loader: 'raw'
13-
}, {
14-
test: /\.css$/, loader: 'style-loader!css-loader'
1513
}]
1614
},
1715
devServer: {
18-
contentBase: 'example/'
16+
contentBase: 'example/',
17+
host: '0.0.0.0'
1918
}
2019
};

widgets-screenshots/range-slider.png

6.99 KB
Loading

widgets/hits.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
var React = require('react');
22

3-
var utils = require('../lib/widget-utils.js');
3+
var utils = require('../lib/utils.js');
44

55
function hits({container = null, templates = {}, hitsPerPage = 20}) {
66
var Hits = require('../components/Hits');

0 commit comments

Comments
 (0)