cspace-ui
Version:
CollectionSpace user interface for browsers
363 lines (299 loc) • 10.6 kB
JSX
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import Immutable from 'immutable';
import SearchResultItemLink from './SearchResultItemLink';
import {
getListType,
searchDescriptorToLocation,
} from '../../helpers/searchHelpers';
import styles from '../../../styles/cspace-ui/SearchResultTraverser.css';
const messages = defineMessages({
result: {
id: 'searchResultTraverser.result',
description: 'Label of the search result link in the search result traverser.',
defaultMessage: 'Search result {current, number} of {total, number}',
},
resultPending: {
id: 'searchResultTraverser.resultPending',
description: 'Label of the search result link in the search result traverser when the search is pending.',
defaultMessage: 'Search result … of …',
},
prev: {
id: 'searchResultTraverser.prev',
description: 'Label of the previous record link in the search result traverser.',
defaultMessage: 'Previous',
},
next: {
id: 'searchResultTraverser.next',
description: 'Label of the next record link in the search result traverser.',
defaultMessage: 'Next',
},
});
const propTypes = {
config: PropTypes.shape({
listTypes: PropTypes.object,
}),
csid: PropTypes.string,
searchName: PropTypes.string,
searchDescriptor: PropTypes.instanceOf(Immutable.Map),
searchState: PropTypes.instanceOf(Immutable.Map),
nextPageSearchDescriptor: PropTypes.instanceOf(Immutable.Map),
nextPageSearchState: PropTypes.instanceOf(Immutable.Map),
prevPageSearchDescriptor: PropTypes.instanceOf(Immutable.Map),
prevPageSearchState: PropTypes.instanceOf(Immutable.Map),
// eslint-disable-next-line react/forbid-prop-types
originSearchPageState: PropTypes.object,
search: PropTypes.func,
};
export default class SearchResultTraverser extends Component {
componentDidMount() {
this.initiateSearch();
}
componentDidUpdate() {
this.initiateSearch();
}
initiateSearch() {
const {
config,
csid,
searchDescriptor,
searchState,
nextPageSearchDescriptor,
nextPageSearchState,
prevPageSearchDescriptor,
prevPageSearchState,
searchName,
search,
} = this.props;
if (search) {
if (searchDescriptor && !searchState) {
// We have a search descriptor, but it's not associated with any state. This happens when
// navigating to a record from a search, and reloading -- the search descriptor is
// maintained in history state, but the search state is gone, since the app has been
// reloaded. Initiate the search.
search(config, searchName, searchDescriptor, getListType(config, searchDescriptor));
}
if (searchState && !searchState.get('isPending') && searchState.get('result')) {
const listType = getListType(config, searchDescriptor);
const listTypeConfig = config.listTypes[listType];
const { listNodeName, itemNodeName } = listTypeConfig;
const indexesByCsid = searchState.get('indexesByCsid');
const index = indexesByCsid.get(csid);
const result = searchState.get('result');
const list = result.get(listNodeName);
const totalItems = list.get('totalItems');
const pageNum = list.get('pageNum');
const pageSize = list.get('pageSize');
const currentNum = (pageNum * pageSize) + index + 1;
const prevIndex = index - 1;
const nextIndex = index + 1;
let items = list.get(itemNodeName);
if (items && !Immutable.List.isList(items)) {
items = Immutable.List.of(items);
}
if (
!prevPageSearchState
&& (prevIndex < 0)
&& (currentNum > 1)
&& prevPageSearchDescriptor
) {
// The previous page has not been retrieved, and we're on the first item of the current
// page. Initiate the search for the previous page.
search(
config,
searchName,
prevPageSearchDescriptor,
getListType(config, prevPageSearchDescriptor),
);
}
if (
!nextPageSearchState
&& (nextIndex > (items.size - 1))
&& (currentNum < totalItems)
&& nextPageSearchDescriptor
) {
// The next page has not been retrieved, and we're on the last item of the current page.
// Initiate the search for the next page.
search(
config,
searchName,
nextPageSearchDescriptor,
getListType(config, nextPageSearchDescriptor),
);
}
}
}
}
renderPrevLink(items, index, currentNum, totalItems, locationState) {
const {
config,
searchName,
prevPageSearchState,
prevPageSearchDescriptor,
} = this.props;
const prevIndex = index - 1;
if (prevIndex >= 0) {
return (
<SearchResultItemLink
rel="prev"
config={config}
item={items.get(prevIndex)}
message={messages.prev}
state={locationState}
/>
);
}
if (prevPageSearchState && !prevPageSearchState.get('isPending') && prevPageSearchState.get('result')) {
// We're at the beginning of the current page, but we have data for the previous page. Link
// to the last item in its results.
const listType = getListType(config, prevPageSearchDescriptor);
const listTypeConfig = config.listTypes[listType];
const { listNodeName, itemNodeName } = listTypeConfig;
let prevPageItems = prevPageSearchState.getIn(['result', listNodeName, itemNodeName]);
if (prevPageItems && !Immutable.List.isList(prevPageItems)) {
prevPageItems = Immutable.List.of(prevPageItems);
}
return (
<SearchResultItemLink
rel="prev"
config={config}
item={prevPageItems.last()}
message={messages.prev}
state={{
searchName,
searchDescriptor: prevPageSearchDescriptor.toJS(),
}}
/>
);
}
if (currentNum > 1) {
// We don't have data for the previous page, but there is one. Show a placeholder link.
return <FormattedMessage {...messages.prev} />;
}
// We're on the first item of all pages. Show a placeholder link.
return <FormattedMessage {...messages.prev} />;
}
renderNextLink(items, index, currentNum, totalItems, locationState) {
const {
config,
searchName,
nextPageSearchState,
nextPageSearchDescriptor,
} = this.props;
const nextIndex = index + 1;
if (nextIndex <= (items.size - 1)) {
return (
<SearchResultItemLink
rel="next"
config={config}
item={items.get(nextIndex)}
message={messages.next}
state={locationState}
/>
);
}
if (nextPageSearchState && !nextPageSearchState.get('isPending') && nextPageSearchState.get('result')) {
// We're at the end of the current page, but we have data for the next page. Link to the
// first item in its results.
const listType = getListType(config, nextPageSearchDescriptor);
const listTypeConfig = config.listTypes[listType];
const { listNodeName, itemNodeName } = listTypeConfig;
let nextPageItems = nextPageSearchState.getIn(['result', listNodeName, itemNodeName]);
if (nextPageItems && !Immutable.List.isList(nextPageItems)) {
nextPageItems = Immutable.List.of(nextPageItems);
}
return (
<SearchResultItemLink
rel="next"
config={config}
item={nextPageItems.first()}
message={messages.next}
state={{
searchName,
searchDescriptor: nextPageSearchDescriptor.toJS(),
}}
/>
);
}
if (currentNum < totalItems) {
// We don't have data for the next page, but there is one. Show a placeholder link.
return <FormattedMessage {...messages.next} />;
}
// We're on the last item of all pages. Show a placeholder link.
return <FormattedMessage {...messages.next} />;
}
render() {
const {
config,
csid,
searchDescriptor,
searchName,
searchState,
originSearchPageState,
} = this.props;
if (!searchDescriptor) {
return null;
}
let resultMessage;
let prevLink;
let nextLink;
if (
!searchState
|| searchState.get('isPending')
|| searchState.get('error')
) {
resultMessage = <FormattedMessage {...messages.resultPending} />;
} else {
const listType = getListType(config, searchDescriptor);
const listTypeConfig = config.listTypes[listType];
const { listNodeName, itemNodeName } = listTypeConfig;
const indexesByCsid = searchState.get('indexesByCsid');
const index = indexesByCsid.get(csid);
const result = searchState.get('result');
const list = result.get(listNodeName);
const totalItems = list.get('totalItems');
const pageNum = list.get('pageNum');
const pageSize = list.get('pageSize');
const currentNum = (pageNum * pageSize) + index + 1;
resultMessage = (
<FormattedMessage
{...messages.result}
values={{ current: currentNum, total: totalItems }}
/>
);
let items = result.getIn([listNodeName, itemNodeName]);
if (items && !Immutable.List.isList(items)) {
items = Immutable.List.of(items);
}
const locationState = {
searchName,
searchDescriptor: searchDescriptor.toJS(),
originSearchPage: originSearchPageState,
};
prevLink = this.renderPrevLink(items, index, currentNum, totalItems, locationState);
nextLink = this.renderNextLink(items, index, currentNum, totalItems, locationState);
}
const searchLocation = Object.assign(searchDescriptorToLocation(searchDescriptor), {
state: {
originSearchPage: originSearchPageState,
},
});
return (
<nav className={styles.common}>
<div>
<Link rel="search" to={searchLocation}>
{resultMessage}
</Link>
</div>
<div>
{prevLink}
{prevLink && nextLink ? ' | ' : null}
{nextLink}
</div>
</nav>
);
}
}
SearchResultTraverser.propTypes = propTypes;