Skip to content

Commit

Permalink
feat(api): add data to CurrentRefinements connector
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
some data were added to CurrentRefinements connector if our computed label isn't the one wanted. See the docs for more information about the new structure.
  • Loading branch information
mthuret committed Nov 15, 2016
1 parent 4588ecc commit ca1ef10
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
.itemLabel {
}

.itemParent {
}

.itemClear {
}
29 changes: 22 additions & 7 deletions packages/react-instantsearch/src/components/CurrentRefinements.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,32 @@ class CurrentRefinements extends Component {
<div {...applyTheme('items', 'items')}>
{items.map(item =>
<div // eslint-disable-line react/jsx-key, automatically done by themeable
{...applyTheme(item.label, 'item')}
{...applyTheme(item.label, 'item', item.items && 'itemParent')}
>
<span {...applyTheme('itemLabel', 'itemLabel')}>
{item.label}
</span>
<button
{...applyTheme('itemClear', 'itemClear')}
onClick={refine.bind(null, [item])}
>
{translate('clearFilter', item)}
</button>
{item.items ?
item.items.map(nestedItem =>
<div // eslint-disable-line react/jsx-key, automatically done by themeable
{...applyTheme(nestedItem.label, 'item')}
>
<span {...applyTheme('itemLabel', 'itemLabel')}>
{nestedItem.label}
</span>
<button
{...applyTheme('itemClear', 'itemClear')}
onClick={refine.bind(null, nestedItem.value)}
>
{translate('clearFilter', nestedItem)}
</button>
</div>) :
<button
{...applyTheme('itemClear', 'itemClear')}
onClick={refine.bind(null, item.value)}
>
{translate('clearFilter', item)}
</button>}
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ import createConnector from '../core/createConnector';
* @kind connector
* @category connector
* @providedPropType {function} refine - a function to remove a single filter
* @providedPropType {array.<{key: string, label: string}>} items - all the filters, the key for calling the refine prop function, label is for the display.
* @providedPropType {array.<{label: string, attributeName: string, currentRefinement: string || object, items: array, value: function}>} items - all the filters, the `value` is to pass to the `refine` function for removing all currentrefinements, `label` is for the display. When existing several refinements for the same atribute name, then you get a nested `items` object that contains a `label` and a `value` function to use to remove a single filter. `attributeName` and `currentRefinement` are metadata containing row values.
*/
export default createConnector({
displayName: 'AlgoliaCurrentRefinements',

getProps(props, state, search, metadata) {
return {
items: metadata.reduce((res, meta) =>
typeof meta.filters !== 'undefined' ? res.concat(meta.filters) : res
, []),
typeof meta.items !== 'undefined' ? res.concat(meta.items) : res
, []),
};
},

refine(props, state, filters) {
return filters.reduce((res, filter) => filter.clear(res), state);
refine(props, state, items) {
// `value` corresponds to our internal clear function computed in each connector metadata.
const clears = items instanceof Array ? items.map(item => item.value) : [items];
return clears.reduce((res, clear) => clear(res), state);
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ const {refine, getProps} = connect;
describe('connectCurrentRefinements', () => {
it('provides the correct props to the component', () => {
const props = getProps(null, null, null, [
{filters: ['one']},
{filters: ['two']},
{filters: ['three']},
{items: ['one']},
{items: ['two']},
{items: ['three']},
]);
expect(props.items).toEqual(['one', 'two', 'three']);
});

it('refine applies the selected filters clear method on state', () => {
const state = refine(null, {wow: 'sweet'}, [{
clear: nextState => ({...nextState, cool: 'neat'}),
value: nextState => ({...nextState, cool: 'neat'}),
}]);
expect(state).toEqual({wow: 'sweet', cool: 'neat'});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function transformValue(value, limit, props, state) {
* @propType {string} [separator='>'] - Specifies the level separator used in the data.
* @propType {string[]} [rootPath=null] - The already selected and hidden path.
* @propType {boolean} [showParentLevel=true] - Flag to set if the parent level should be displayed.
* @providedPropType {function} refine - a function to remove a single filter
* @providedPropType {function} refine - a function to select a refinement
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied
* @providedPropType {array.<{children: object, count: number, isRefined: boolean, label: string, value: string}>} items - the list of items the HierarchicalMenu can display. Children has the same shape as parent items.
Expand Down Expand Up @@ -204,12 +204,14 @@ export default createConnector({
const currentRefinement = getCurrentRefinement(props, state);
return {
id,
filters: !currentRefinement ? [] : [{
items: !currentRefinement ? [] : [{
label: `${id}: ${currentRefinement}`,
clear: nextState => ({
attributeName: id,
value: nextState => ({
...nextState,
[id]: '',
}),
currentRefinement,
}],
};
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,21 +196,23 @@ describe('connectHierarchicalMenu', () => {

it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
expect(metadata).toEqual({id: 'ok', filters: []});
expect(metadata).toEqual({items: [], id: 'ok'});
});

it('registers its filter in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {ok: 'wat'});
expect(metadata).toEqual({
id: 'ok',
filters: [{
items: [{
label: 'ok: wat',
attributeName: 'ok',
currentRefinement: 'wat',
// Ignore clear, we test it later
clear: metadata.filters[0].clear,
value: metadata.items[0].value,
}],
});

const state = metadata.filters[0].clear({ok: 'wat'});
const state = metadata.items[0].value({ok: 'wat'});
expect(state).toEqual({ok: ''});
});
});
8 changes: 5 additions & 3 deletions packages/react-instantsearch/src/connectors/connectMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function getCurrentRefinement(props, state) {
* @propType {number} [limitMax=20] - the maximun number of displayed items. Only used when showMore is set to `true`
* @propType {string[]} [sortBy=['count:desc','name:asc']] - defines how the items are sorted. See [the helper documentation](https://community.algolia.com/algoliasearch-helper-js/reference.html#specifying-a-different-sort-order-for-values) for the full list of options
* @propType {string} defaultRefinement - the value of the item selected by default
* @providedPropType {function} refine - a function to remove a single filter
* @providedPropType {function} refine - a function to select a refinement
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied
* @providedPropType {array.<{count: number, isRefined: boolean, label: string, value: string}>} items - the list of items the Menu can display.
Expand Down Expand Up @@ -124,12 +124,14 @@ export default createConnector({
const currentRefinement = getCurrentRefinement(props, state);
return {
id,
filters: currentRefinement === null ? [] : [{
items: currentRefinement === null ? [] : [{
label: `${props.attributeName}: ${currentRefinement}`,
clear: nextState => ({
attributeName: props.attributeName,
value: nextState => ({
...nextState,
[id]: '',
}),
currentRefinement,
}],
};
},
Expand Down
10 changes: 6 additions & 4 deletions packages/react-instantsearch/src/connectors/connectMenu.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,21 +150,23 @@ describe('connectMenu', () => {

it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
expect(metadata).toEqual({id: 'ok', filters: []});
expect(metadata).toEqual({id: 'ok', items: []});
});

it('registers its filter in metadata', () => {
const metadata = getMetadata({id: 'ok', attributeName: 'wot'}, {ok: 'wat'});
expect(metadata).toEqual({
id: 'ok',
filters: [{
items: [{
label: 'wot: wat',
attributeName: 'wot',
currentRefinement: 'wat',
// Ignore clear, we test it later
clear: metadata.filters[0].clear,
value: metadata.items[0].value,
}],
});

const state = metadata.filters[0].clear({ok: 'wat'});
const state = metadata.items[0].value({ok: 'wat'});
expect(state).toEqual({ok: ''});
});
});
12 changes: 7 additions & 5 deletions packages/react-instantsearch/src/connectors/connectMultiRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function getCurrentRefinement(props, state) {
* @propType {string} attributeName - the name of the attribute in the records
* @propType {{label: string, start: number, end: number}[]} items - List of options. With a text label, and upper and lower bounds.
* @propType {string} defaultRefinement - the value of the item selected by default, follow the shape of a `string` with a pattern of `'{start}:{end}'`.
* @providedPropType {function} refine - a function to remove a single filter
* @providedPropType {function} refine - a function to select a range.
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied. follow the shape of a `string` with a pattern of `'{start}:{end}'` which corresponds to the current selected item. For instance, when the selected item is `{start: 10, end: 20}`, the state of the widget is `'10:20'`. When `start` isn't defined, the state of the widget is `':{end}'`, and the same way around when `end` isn't defined. However, when neither `start` nor `end` are defined, the state is an empty string.
* @providedPropType {array.<{isRefined: boolean, label: string, value: string}>} items - the list of ranges the MultiRange can display.
Expand Down Expand Up @@ -113,17 +113,19 @@ export default createConnector({
getMetadata(props, state) {
const id = getId(props);
const value = getCurrentRefinement(props, state);
const filters = [];
const items = [];
if (value !== '') {
const {label} = find(props.items, item => stringifyItem(item) === value);
filters.push({
items.push({
label: `${props.attributeName}: ${label}`,
clear: nextState => ({
attributeName: props.attributeName,
currentRefinement: label,
value: nextState => ({
...nextState,
[id]: '',
}),
});
}
return {id, filters};
return {id, items};
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ describe('connectMultiRange', () => {

it('registers its id in metadata', () => {
const metadata = getMetadata({id: 'ok'}, {});
expect(metadata).toEqual({id: 'ok', filters: []});
expect(metadata).toEqual({id: 'ok', items: []});
});

it('registers its filter in metadata', () => {
Expand All @@ -135,14 +135,16 @@ describe('connectMultiRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
filters: [{
items: [{
label: 'wot: YAY',
// Ignore clear, we test it later
clear: metadata.filters[0].clear,
value: metadata.items[0].value,
attributeName: 'wot',
currentRefinement: 'YAY',
}],
});

const state = metadata.filters[0].clear({wot: '100:200'});
const state = metadata.items[0].value({wot: '100:200'});
expect(state).toEqual({wot: ''});
});
});
22 changes: 12 additions & 10 deletions packages/react-instantsearch/src/connectors/connectRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import createConnector from '../core/createConnector';
* @propType {{min: number, max: number}} defaultRefinement - Default state of the widget containing the start and the end of the range.
* @propType {number} min - Minimum value. When this isn't set, the minimum value will be automatically computed by Algolia using the data in the index.
* @propType {number} max - Maximum value. When this isn't set, the maximum value will be automatically computed by Algolia using the data in the index.
* @providedPropType {function} refine - a function to remove a single filter
* @providedPropType {function} refine - a function to select a range.
* @providedPropType {function} createURL - a function to generate a URL for the corresponding state
* @providedPropType {string} currentRefinement - the refinement currently applied
*/
Expand Down Expand Up @@ -125,21 +125,23 @@ export default createConnector({
getMetadata(props, state) {
const id = getId(props);
const currentRefinement = getCurrentRefinement(props, state);
let filter;
let item;
const hasMin = typeof currentRefinement.min !== 'undefined';
const hasMax = typeof currentRefinement.max !== 'undefined';
if (hasMin || hasMax) {
let filterLabel = '';
let itemLabel = '';
if (hasMin) {
filterLabel += `${currentRefinement.min} <= `;
itemLabel += `${currentRefinement.min} <= `;
}
filterLabel += props.attributeName;
itemLabel += props.attributeName;
if (hasMax) {
filterLabel += ` <= ${currentRefinement.max}`;
itemLabel += ` <= ${currentRefinement.max}`;
}
filter = {
label: filterLabel,
clear: nextState => ({
item = {
label: itemLabel,
currentRefinement,
attributeName: props.attributeName,
value: nextState => ({
...nextState,
[id]: {},
}),
Expand All @@ -148,7 +150,7 @@ export default createConnector({

return {
id,
filters: filter ? [filter] : [],
items: item ? [item] : [],
};
},
});
20 changes: 13 additions & 7 deletions packages/react-instantsearch/src/connectors/connectRange.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,16 @@ describe('connectRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
filters: [{
items: [{
label: '5 <= wot',
attributeName: 'wot',
currentRefinement: {min: 5, max: undefined},
// Ignore clear, we test it later
clear: metadata.filters[0].clear,
value: metadata.items[0].value,
}],
});

const state = metadata.filters[0].clear({wot: {min: 5}});
const state = metadata.items[0].value({wot: {min: 5}});
expect(state).toEqual({wot: {}});

metadata = getMetadata(
Expand All @@ -125,9 +127,11 @@ describe('connectRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
filters: [{
items: [{
label: 'wot <= 10',
clear: metadata.filters[0].clear,
attributeName: 'wot',
currentRefinement: {min: undefined, max: 10},
value: metadata.items[0].value,
}],
});

Expand All @@ -137,9 +141,11 @@ describe('connectRange', () => {
);
expect(metadata).toEqual({
id: 'wot',
filters: [{
items: [{
label: '5 <= wot <= 10',
clear: metadata.filters[0].clear,
attributeName: 'wot',
currentRefinement: {min: 5, max: 10},
value: metadata.items[0].value,
}],
});
});
Expand Down
Loading

0 comments on commit ca1ef10

Please sign in to comment.