Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): add data to CurrentRefinements connector #1550

Merged
merged 1 commit into from
Nov 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 refinementsToClear = items instanceof Array ? items.map(item => item.value) : [items];
return refinementsToClear.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 toggle 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 toggle 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