Skip to content

Commit

Permalink
initialState: add sort on empty query
Browse files Browse the repository at this point in the history
  • Loading branch information
KonstantinaStoikou committed Oct 6, 2020
1 parent 4630972 commit 3b8e62c
Show file tree
Hide file tree
Showing 23 changed files with 136 additions and 87 deletions.
36 changes: 15 additions & 21 deletions docs/docs/components/react_search_kit.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ It provides state and configuration to the application.
## Usage

```jsx
<ReactSearchKit
searchApi={searchApi}
>
<ReactSearchKit searchApi={searchApi}>
... React-SearchKit components ...
</ReactSearchKit>
```
Expand All @@ -21,37 +19,33 @@ See the [complete guide](main_concepts.md) for detailed information.

## Props

* **searchApi** `object` *required*
- **searchApi** `object` _required_

An instance of the adapter class for your search backend.

* **urlHandlerApi** `object` *optional*
- **urlHandlerApi** `object` _optional_

An object containing configuration for handling URL parameters:

* **enabled** `boolean`: `true` if URL parameters should be updated `false` otherwise. Default `true`.
* **overrideConfig** `object`:
- **enabled** `boolean`: `true` if URL parameters should be updated `false` otherwise. Default `true`.
- **overrideConfig** `object`:

* **keepHistory** `boolean`: `true` if each change of the URL parameters should push a new state to the browser history, `false` if it should replace it instead. Default `true`.
* **urlFilterSeparator** `object`: a character(s) to override the default character defined in `UrlHandlerApi`, used when separating child filters.
* **urlParamsMapping** `object`: an object to override the default mapping defined in `UrlHandlerApi`, used when serializing each query state field to an URL parameter.
* **urlParamValidator** `object`: an object to override the default implementation of `UrlParamValidator` in `UrlHandlerApi`.
* **urlParser** `object`: an object to override the default implementation of `UrlParser` in `UrlHandlerApi`.
- **keepHistory** `boolean`: `true` if each change of the URL parameters should push a new state to the browser history, `false` if it should replace it instead. Default `true`.
- **urlFilterSeparator** `object`: a character(s) to override the default character defined in `UrlHandlerApi`, used when separating child filters.
- **urlParamsMapping** `object`: an object to override the default mapping defined in `UrlHandlerApi`, used when serializing each query state field to an URL parameter.
- **urlParamValidator** `object`: an object to override the default implementation of `UrlParamValidator` in `UrlHandlerApi`.
- **urlParser** `object`: an object to override the default implementation of `UrlParser` in `UrlHandlerApi`.

* **customHandler** `object`: override entirely the default class `UrlHandlerApi`.
- **customHandler** `object`: override entirely the default class `UrlHandlerApi`.

* **searchOnInit** `object` *optional*
- **searchOnInit** `object` _optional_

A boolean to perform a search when the application is mounted. Default `true`.

* **defaultSortByOnEmptyQuery** `boolean` *optional*

A boolean to define a default `sort by` value when the query string is empty. This is useful when the results sorting should be different when the user inserts a query string or not (e.g. `most recent` or `most relevant` first).

* **appName** `string` *optional*
- **appName** `string` _optional_

A name identifier to distinguish uniquely the application. Default `RSK`.

* **eventListenerEnabled** `boolean` *optional*
- **eventListenerEnabled** `boolean` _optional_

If `true` the application listens to the `queryChanged` event else if `false` no listener is registered. When this event is emitted the application triggers a search based on the payload that is passed to the event at the emission time. Default `false`.
If `true` the application listens to the `queryChanged` event else if `false` no listener is registered. When this event is emitted the application triggers a search based on the payload that is passed to the event at the emission time. Default `false`.
5 changes: 0 additions & 5 deletions docs/docs/components/results_per_page.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ The component is **not** displayed while executing the search query or if there
```jsx
<ResultsPerPage
values={[{text: "Ten", value: 10}, {text: "Twenty", value: 20}]}
defaultValue={20}
/>
```

Expand All @@ -22,10 +21,6 @@ The component is **not** displayed while executing the search query or if there

A list of possible values, where each value has the format `{ text: "Fifty", value: 50 }`.

* **defaultValue** `String`

The default value to pre-select when rendering the component. For example, `20`.

* **renderElement** `function` *optional*

An optional function to override the default rendered component.
Expand Down
9 changes: 0 additions & 9 deletions docs/docs/components/sort_by.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ The component is **not** displayed while executing the search query or if there
```jsx
<SortBy
values={[{text: "Most recent", value: "created"}, {text: "Title", value: "title"}]}
defaultValue="title"
/>
```

Expand All @@ -22,14 +21,6 @@ The component is **not** displayed while executing the search query or if there

A list of possible values, where each value has the format `{ text: "Creation Date", value: "created" }`.

* **defaultValue** `String`

The default value to pre-select when rendering the component. For example, `"created"`.

* **defaultSortByOnEmptyQuery** `String` *optional*

Value to use when executing a search with an empty query string. When searching with an empty query, users normally expect most recent results, while searching with a defined query string, best match or any other sorting field. Default value: the value of `defaultValue` prop.

- **label** `function` _optional_

An optional function to wrap the component with a prefix and suffix string. <br />
Expand Down
5 changes: 0 additions & 5 deletions docs/docs/components/sort_order.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ The component is **not** displayed while executing the search query or if there
```jsx
<SortOrder
values={[{text: "Asc", value: "asc"}, {text: "Desc", value: "desc"}]}
defaultValue="desc"
/>
```

Expand All @@ -22,10 +21,6 @@ The component is **not** displayed while executing the search query or if there

A list of possible values, where each value has the format `{ text: "Asc", value: "asc" }`.

* **defaultValue** `String`

The default value to pre-select when rendering the component. For example, `"desc"`.

- **label** `function` _optional_

An optional function to wrap the component with a prefix and suffix string. <br />
Expand Down
2 changes: 0 additions & 2 deletions docs/docs/main_concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ The application is split in 3 main parts:
Components interact with the app using the standard Redux data flow. Each component is connected to part of the application state and receives only the data and the actions that it needs and should responsible for.
For example, the `SearchBar` component receives the current query string stored in the state and can perform the action to update it.

Some components do have also extra properties, such as default values. For example, the `SortBy` component receive a prop `defaultValue`: when mounting the component, a specific action will be fired to set the provided value as the initial state for the `sortBy` field.

You can find the list of components in the [Components](components/react_search_kit.md) section.

### Look And Feel
Expand Down
1 change: 0 additions & 1 deletion src/demos/cern-videos/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const sortValues = [
text: 'Newest',
sortBy: 'mostrecent',
sortOrder: 'asc',
default: true,
},
{
text: 'Oldest',
Expand Down
1 change: 0 additions & 1 deletion src/demos/cern-videos/Results.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export class Results extends Component {
<ResultsPerPage
values={this.resultsPerPageValues}
label={(cmp) => <> Show {cmp} results per page</>}
defaultValue={10}
/>
</span>
<LayoutSwitcher defaultLayout="grid" />
Expand Down
3 changes: 1 addition & 2 deletions src/demos/zenodo/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const sortValues = [
text: 'Most viewed',
sortBy: 'mostviewed',
sortOrder: 'asc',
defaultOnEmptyString: true,
},
{
text: 'Least viewed',
Expand All @@ -49,7 +48,6 @@ const sortValues = [
text: 'Newest',
sortBy: 'mostrecent',
sortOrder: 'asc',
default: true,
},
{
text: 'Oldest',
Expand Down Expand Up @@ -84,6 +82,7 @@ const searchApi = new InvenioSearchApi({
const initialState = {
sortBy: 'mostrecent',
sortOrder: 'asc',
sortByOnEmptyQuery: 'mostviewed',
layout: 'list',
page: 1,
size: 10,
Expand Down
1 change: 0 additions & 1 deletion src/demos/zenodo/Results.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export class Results extends Component {
<span style={({ marginLeft: '0.5em' }, { marginRight: '0.5em' })}>
<ResultsPerPage
values={this.resultsPerPageValues}
defaultValue={10}
label={(cmp) => <>Show {cmp} results per page</>}
/>
</span>
Expand Down
26 changes: 22 additions & 4 deletions src/lib/components/ReactSearchKit/ReactSearchKit.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,37 @@ import { buildUID } from '../../util';
export class ReactSearchKit extends Component {
constructor(props) {
super(props);

const validate = (initialQueryState) => {
const layoutOptions = ['list', 'grid'];
if (
initialQueryState['layout'] &&
!layoutOptions.includes(initialQueryState['layout'])
) {
throw new Error('Layout option is invalid.');
}

initialQueryState['sortByOnEmptyQuery'] =
initialQueryState['sortByOnEmptyQuery'] || initialQueryState['sortBy'];

initialQueryState['sortOrderOnEmptyQuery'] =
initialQueryState['sortOrderOnEmptyQuery'] ||
initialQueryState['sortOrder'];

return initialQueryState;
};

const appConfig = {
searchApi: props.searchApi,
suggestionApi: props.suggestionApi,
urlHandlerApi: props.urlHandlerApi.enabled
? props.urlHandlerApi.customHandler ||
new UrlHandlerApi(props.urlHandlerApi.overrideConfig)
: null,
defaultSortByOnEmptyQuery: props.defaultSortByOnEmptyQuery,
searchOnInit: props.searchOnInit,
initialQueryState: props.initialQueryState,
initialQueryState: validate(props.initialQueryState),
};

this.store = configureStore(appConfig);
this.appName = props.appName;
this.eventListenerEnabled = props.eventListenerEnabled;
Expand Down Expand Up @@ -68,7 +88,6 @@ ReactSearchKit.propTypes = {
customHandler: PropTypes.object,
}),
searchOnInit: PropTypes.bool,
defaultSortByOnEmptyQuery: PropTypes.string,
appName: PropTypes.string,
eventListenerEnabled: PropTypes.bool,
overridableId: PropTypes.string,
Expand All @@ -83,7 +102,6 @@ ReactSearchKit.defaultProps = {
customHandler: null,
},
searchOnInit: true,
defaultSortByOnEmptyQuery: null,
appName: 'RSK',
eventListenerEnabled: false,
overridableId: '',
Expand Down
30 changes: 26 additions & 4 deletions src/lib/components/ReactSearchKit/ReactSearchKit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ beforeEach(() => {

const searchApi = new (class {})();

const initialQueryState = {};

describe('test ReactSearchKit component', () => {
it('should use default configuration', () => {
const rsk = shallow(<ReactSearchKit searchApi={searchApi} />);
Expand All @@ -61,7 +63,6 @@ describe('test ReactSearchKit component', () => {
expect.objectContaining({
searchApi: searchApi,
urlHandlerApi: null,
defaultSortByOnEmptyQuery: null,
})
);

Expand Down Expand Up @@ -104,6 +105,22 @@ describe('test ReactSearchKit component', () => {
});
});

it('should use inject initialQueryState in the configuration', () => {
shallow(
<ReactSearchKit
searchApi={searchApi}
initialQueryState={initialQueryState}
/>
);

expect(configureStore).toBeCalledWith(
expect.objectContaining({
searchApi: searchApi,
initialQueryState: initialQueryState,
})
);
});

it('should use custom class for UrlHandlerApi when provided', () => {
const mockedCustomUrlHandlerApi = new (class {})();
shallow(
Expand All @@ -124,15 +141,20 @@ describe('test ReactSearchKit component', () => {
);
});

it('should use inject defaultSortByOnEmptyQuery in the configuration', () => {
it('should use layout options in the initialQueryState', () => {
shallow(
<ReactSearchKit searchApi={searchApi} defaultSortByOnEmptyQuery="value" />
<ReactSearchKit
searchApi={searchApi}
initialQueryState={{
layout: 'grid',
}}
/>
);

expect(configureStore).toBeCalledWith(
expect.objectContaining({
searchApi: searchApi,
defaultSortByOnEmptyQuery: 'value',
initialQueryState: { layout: 'grid' },
})
);
});
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/SearchBar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { connect } from '../../store';
import { updateQueryString } from '../../state/actions';
import SearchBarComponent from './SearchBar';

const mapDispatchToProps = dispatch => ({
updateQueryString: query => dispatch(updateQueryString(query)),
const mapDispatchToProps = (dispatch) => ({
updateQueryString: (query) => dispatch(updateQueryString(query)),
});

export const SearchBar = connect(
state => ({
(state) => ({
queryString: state.query.queryString,
}),
mapDispatchToProps
Expand Down
17 changes: 15 additions & 2 deletions src/lib/components/Sort/Sort.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ class Sort extends Component {
totalResults,
label,
overridableId,
currentQueryString,
sortByOnEmptyQuery,
sortOrderOnEmptyQuery,
} = this.props;

const sortBy =
currentQueryString !== '' ? currentSortBy : sortByOnEmptyQuery;

const sortOrder =
currentQueryString !== '' ? currentSortOrder : sortOrderOnEmptyQuery;

return (
<ShouldRender
condition={
Expand All @@ -59,8 +69,8 @@ class Sort extends Component {
>
{label(
<Element
currentSortBy={currentSortBy}
currentSortOrder={currentSortOrder}
currentSortBy={sortBy}
currentSortOrder={sortOrder}
options={this.options}
onValueChange={this.onChange}
computeValue={this._computeValue}
Expand All @@ -76,9 +86,12 @@ Sort.propTypes = {
values: PropTypes.array.isRequired,
currentSortBy: PropTypes.string,
currentSortOrder: PropTypes.string,
currentQueryString: PropTypes.string.isRequired,
loading: PropTypes.bool.isRequired,
totalResults: PropTypes.number.isRequired,
updateQuerySorting: PropTypes.func.isRequired,
sortByOnEmptyQuery: PropTypes.string.isRequired,
sortOrderOnEmptyQuery: PropTypes.string.isRequired,
label: PropTypes.func,
overridableId: PropTypes.string,
};
Expand Down
2 changes: 2 additions & 0 deletions src/lib/components/Sort/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const Sort = connect(
currentSortOrder: state.query.sortOrder,
loading: state.results.loading,
totalResults: state.results.data.total,
sortByOnEmptyQuery: state.initialState.sortByOnEmptyQuery,
sortOrderOnEmptyQuery: state.initialState.sortOrderOnEmptyQuery,
}),
mapDispatchToProps
)(SortComponent);
8 changes: 7 additions & 1 deletion src/lib/components/SortBy/SortBy.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,20 @@ class SortBy extends Component {
totalResults,
label,
overridableId,
currentQueryString,
sortByOnEmptyQuery,
} = this.props;

const sortBy =
currentQueryString !== '' ? currentSortBy : sortByOnEmptyQuery;

return (
<ShouldRender
condition={currentSortBy !== null && !loading && totalResults > 0}
>
{label(
<Element
currentSortBy={currentSortBy}
currentSortBy={sortBy}
options={this.options}
onValueChange={this.onChange}
overridableId={overridableId}
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/SortBy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const SortBy = connect(
totalResults: state.results.data.total,
currentSortBy: state.query.sortBy,
currentQueryString: state.query.queryString,
sortByOnEmptyQuery: state.initialState.sortByOnEmptyQuery,
}),
mapDispatchToProps
)(SortByComponent);

0 comments on commit 3b8e62c

Please sign in to comment.