Skip to content

Commit

Permalink
feat(connectors): connectRangeSlider (iteration2)
Browse files Browse the repository at this point in the history
  • Loading branch information
iam4x committed Mar 21, 2017
1 parent 12a7935 commit 6073d94
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 218 deletions.
19 changes: 5 additions & 14 deletions src/connectors/range-slider/__tests__/connectRangeSlider-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@


import sinon from 'sinon';

import jsHelper from 'algoliasearch-helper';
Expand Down Expand Up @@ -36,6 +34,7 @@ describe('connectRangeSlider', () => {
state: helper.state,
createURL: () => '#',
onHistoryChange: () => {},
instantSearchInstance: {templatesConfig: undefined},
});

{ // should call the rendering once with isFirstRendering to true
Expand All @@ -44,20 +43,16 @@ describe('connectRangeSlider', () => {
expect(isFirstRendering).toBe(true);

// should provide good values for the first rendering
const {range, collapsible, start,
shouldAutoHideContainer, containerNode} = rendering.lastCall.args[0];
const {range, start} = rendering.lastCall.args[0];
expect(range).toEqual({min: 0, max: 0});
expect(start).toEqual([-Infinity, Infinity]);
expect(collapsible).toBe(false);
expect(shouldAutoHideContainer).toBe(true);
expect(containerNode).toBe(container);
}

widget.render({
results: new SearchResults(helper.state, [{
hits: [{test: 'oneTime'}],
facets: {price: {10: 1, 20: 1, 30: 1}},
facets_stats: { // eslint-disable-line
facets_stats: { // eslint-disable-line
price: {
avg: 20,
max: 30,
Expand All @@ -80,13 +75,9 @@ describe('connectRangeSlider', () => {
expect(isFirstRendering).toBe(false);

// should provide good values for the first rendering
const {range, collapsible, start,
shouldAutoHideContainer, containerNode} = rendering.lastCall.args[0];
const {range, start} = rendering.lastCall.args[0];
expect(range).toEqual({min: 10, max: 30});
expect(start).toEqual([-Infinity, Infinity]);
expect(collapsible).toBe(false);
expect(shouldAutoHideContainer).toBe(false);
expect(containerNode).toBe(container);
}
});

Expand Down Expand Up @@ -158,7 +149,7 @@ describe('connectRangeSlider', () => {
results: new SearchResults(helper.state, [{
hits: [{test: 'oneTime'}],
facets: {price: {10: 1, 20: 1, 30: 1}},
facets_stats: { // eslint-disable-line
facets_stats: { // eslint-disable-line
price: {
avg: 20,
max: 30,
Expand Down
291 changes: 126 additions & 165 deletions src/connectors/range-slider/connectRangeSlider.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import {
bemHelper,
prepareTemplateProps,
getContainerNode,
} from '../../lib/utils.js';
import find from 'lodash/find';
import cx from 'classnames';

const bem = bemHelper('ais-range-slider');
const defaultTemplates = {
header: '',
footer: '',
};
import {checkRendering} from '../../lib/utils.js';

const usage = `Usage:
var customRangeSlider = connectRangeSlider(function render(params, isFirstRendering) {
// params = {
// refine,
// range,
// start,
// instantSearchInstance,
// }
});
search.addWidget(
customRangeSlider({
attributeName,
min,
max,
precision
});
);
Full documentation available at https://community.algolia.com/instantsearch.js/connectors/connectRangeSlider.html
`;

/**
* Instantiate a slider based on a numeric attribute.
Expand Down Expand Up @@ -40,165 +48,118 @@ const defaultTemplates = {
* @param {number} [options.max] Maximal slider value, defaults to automatically computed from the result set
* @return {Object}
*/
const usage = `Usage:
rangeSlider({
container,
attributeName,
[ tooltips=true ],
[ templates.{header, footer} ],
[ cssClasses.{root, header, body, footer} ],
[ step=1 ],
[ pips=true ],
[ autoHideContainer=true ],
[ collapsible=false ],
[ min ],
[ max ]
});
`;
const connectRangeSlider = rangeSliderRendering => ({
container,

export default function connectRangeSlider(renderFn) {
checkRendering(renderFn, usage);

return ({
attributeName,
tooltips = true,
templates = defaultTemplates,
collapsible = false,
cssClasses: userCssClasses = {},
step = 1,
pips = true,
autoHideContainer = true,
min: userMin,
max: userMax,
precision = 2,
} = {}) => {
if (!container || !attributeName) {
throw new Error(usage);
}
}) => {
if (!attributeName) {
throw new Error(usage);
}

const formatToNumber = v => Number(Number(v).toFixed(precision));

const sliderFormatter = {
from: v => v,
to: v => formatToNumber(v).toLocaleString(),
};

return {
getConfiguration: originalConf => {
const conf = {
disjunctiveFacets: [attributeName],
};

const hasUserBounds = userMin !== undefined || userMax !== undefined;
const boundsNotAlreadyDefined = !originalConf ||
originalConf.numericRefinements &&
originalConf.numericRefinements[attributeName] === undefined;

if (hasUserBounds && boundsNotAlreadyDefined) {
conf.numericRefinements = {[attributeName]: {}};
if (userMin !== undefined) conf.numericRefinements[attributeName]['>='] = [userMin];
if (userMax !== undefined) conf.numericRefinements[attributeName]['<='] = [userMax];
}

const formatToNumber = v => Number(Number(v).toFixed(precision));
return conf;
},

const sliderFormatter = {
from: v => v,
to: v => formatToNumber(v).toLocaleString(),
};
_getCurrentRefinement(helper) {
let min = helper.state.getNumericRefinement(attributeName, '>=');
let max = helper.state.getNumericRefinement(attributeName, '<=');

const containerNode = getContainerNode(container);

const cssClasses = {
root: cx(bem(null), userCssClasses.root),
header: cx(bem('header'), userCssClasses.header),
body: cx(bem('body'), userCssClasses.body),
footer: cx(bem('footer'), userCssClasses.footer),
};

return {
getConfiguration: originalConf => {
const conf = {
disjunctiveFacets: [attributeName],
};

const hasUserBounds = userMin !== undefined || userMax !== undefined;
const boundsNotAlreadyDefined = !originalConf ||
originalConf.numericRefinements &&
originalConf.numericRefinements[attributeName] === undefined;

if (hasUserBounds && boundsNotAlreadyDefined) {
conf.numericRefinements = {[attributeName]: {}};
if (userMin !== undefined) conf.numericRefinements[attributeName]['>='] = [userMin];
if (userMax !== undefined) conf.numericRefinements[attributeName]['<='] = [userMax];
}

return conf;
},
_getCurrentRefinement(helper) {
let min = helper.state.getNumericRefinement(attributeName, '>=');
let max = helper.state.getNumericRefinement(attributeName, '<=');

if (min && min.length) {
min = min[0];
} else {
min = -Infinity;
}

if (max && max.length) {
max = max[0];
} else {
max = Infinity;
}

return {
min,
max,
};
},
init({helper, templatesConfig}) {
this._templateProps = prepareTemplateProps({
defaultTemplates,
templatesConfig,
templates,
});
this._refine = bounds => newValues => {
helper.clearRefinements(attributeName);
if (!bounds.min || newValues[0] > bounds.min) {
helper.addNumericRefinement(attributeName, '>=', formatToNumber(newValues[0]));
if (min && min.length) {
min = min[0];
} else {
min = -Infinity;
}
if (!bounds.max || newValues[1] < bounds.max) {
helper.addNumericRefinement(attributeName, '<=', formatToNumber(newValues[1]));

if (max && max.length) {
max = max[0];
} else {
max = Infinity;
}
helper.search();
};

const stats = {
min: userMin || null,
max: userMax || null,
};
const currentRefinement = this._getCurrentRefinement(helper);

rangeSliderRendering({
collapsible,
cssClasses,
refine: this._refine(stats),
pips,
range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
shouldAutoHideContainer: autoHideContainer && stats.min === stats.max,
start: [currentRefinement.min, currentRefinement.max],
step,
templateProps: this._templateProps,
tooltips,
format: sliderFormatter,
containerNode,
}, true);
},
render({results, helper}) {
const facet = find(results.disjunctiveFacets, {name: attributeName});
const stats = facet !== undefined && facet.stats !== undefined ? facet.stats : {
min: null,
max: null,
};

if (userMin !== undefined) stats.min = userMin;
if (userMax !== undefined) stats.max = userMax;

const currentRefinement = this._getCurrentRefinement(helper);

if (tooltips.format !== undefined) {
tooltips = [{to: tooltips.format}, {to: tooltips.format}];
}

rangeSliderRendering({
collapsible,
cssClasses,
refine: this._refine(stats),
pips,
range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
shouldAutoHideContainer: autoHideContainer && stats.min === stats.max,
start: [currentRefinement.min, currentRefinement.max],
step,
templateProps: this._templateProps,
tooltips,
format: sliderFormatter,
containerNode,
}, false);
},
};
};

export default connectRangeSlider;
return {
min,
max,
};
},

init({helper, instantSearchInstance}) {
this._instantSearchInstance = instantSearchInstance;

this._refine = bounds => newValues => {
helper.clearRefinements(attributeName);
if (!bounds.min || newValues[0] > bounds.min) {
helper.addNumericRefinement(attributeName, '>=', formatToNumber(newValues[0]));
}
if (!bounds.max || newValues[1] < bounds.max) {
helper.addNumericRefinement(attributeName, '<=', formatToNumber(newValues[1]));
}
helper.search();
};

const stats = {
min: userMin || null,
max: userMax || null,
};
const currentRefinement = this._getCurrentRefinement(helper);

renderFn({
refine: this._refine(stats),
range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
start: [currentRefinement.min, currentRefinement.max],
format: sliderFormatter,
instantSearchInstance: this._instantSearchInstance,
}, true);
},

render({results, helper}) {
const facet = (results.disjunctiveFacets || []).find(({name}) => name === attributeName);
const stats = facet !== undefined && facet.stats !== undefined ? facet.stats : {
min: null,
max: null,
};

if (userMin !== undefined) stats.min = userMin;
if (userMax !== undefined) stats.max = userMax;

const currentRefinement = this._getCurrentRefinement(helper);

renderFn({
refine: this._refine(stats),
range: {min: Math.floor(stats.min), max: Math.ceil(stats.max)},
start: [currentRefinement.min, currentRefinement.max],
format: sliderFormatter,
instantSearchInstance: this._instantSearchInstance,
}, false);
},
};
};
}
Loading

0 comments on commit 6073d94

Please sign in to comment.