Commit 2b0aa65d authored by Matija Obreza's avatar Matija Obreza
Browse files

Don't paginate vocabulary terms, keep loading them as user scrolls

- New `<PagedLoader` component
parent a81c3972
......@@ -12649,6 +12649,15 @@
"prop-types": "15.6.0"
}
},
"react-visibility-sensor": {
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/react-visibility-sensor/-/react-visibility-sensor-3.11.0.tgz",
"integrity": "sha512-4ZLCYR50zGKH2WgsenuOaQTlCOkc69z882RaDGPpYAJqCY6ZXshzFhEuyv/LTPwoj6VBt8sSJRdvg0x+TeuyQA==",
"requires": {
"create-react-class": "15.6.3",
"prop-types": "15.6.0"
}
},
"read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
......
import * as React from 'react';
import {log} from 'utilities/debug';
import { Page } from 'model/common.model';
import * as VisibilitySensor from 'react-visibility-sensor';
interface IProps<T> extends React.Props<any> {
paged: Page<T>;
colSpan?: number; // if in table, set the colSpan to render <tr><td colSpan=...
offsetTop?: number; // px before end of list when next page should start loading, defaults to 300px
itemRenderer: (item: T, index?: number) => any;
loadingIndicator: any;
loadPage: (page: number, pageSize: number) => Promise<Page<T>>;
}
export default class PagedLoader<T> extends React.Component<IProps<T>, any> {
public constructor(props: any) {
super(props);
const { paged: { number: pageNumber, content: newContent } } = this.props;
this.state = {
list: [ ...newContent ],
pageNumber,
loading: false,
};
}
public componentWillMount() {
// ?
}
public componentWillReceiveProps(nextProps) {
log('nextProps', nextProps);
const { paged: { number: pageNumber, content: newContent } } = nextProps;
if (newContent) {
this.setState({
...this.state,
list: [ ...newContent ],
pageNumber,
loading: false,
});
}
}
private endOfListVisibilityChange = (isVisible: boolean): void => {
const { paged, loadPage } = this.props;
const { list, pageNumber, loading } = this.state;
// log(`Visibility ${isVisible}`);
if (paged && isVisible) {
// we should load some stuff
if (! loading && paged.totalElements > list.length) {
// log('Calling for next page', pageNumber + 1);
this.setState({
...this.state,
loading: true,
});
loadPage(pageNumber + 1, paged.size)
.then((nextPage) => {
log('Received next page', nextPage);
this.setState({
...this.state,
list: [ ...this.state.list, ...nextPage.content ],
pageNumber: nextPage.number,
loading: false,
});
// log('State', this.state, this.state.list);
});
}
}
}
public render() {
const { list, loading } = this.state;
const { itemRenderer, loadingIndicator, colSpan, offsetTop } = this.props;
if (! list || list.length === 0) {
return null;
}
log(`Rendering ${list.length} items`);
const inTable = colSpan ? true : false;
const visibilityOffset = offsetTop && offsetTop || 1000;
// log(`Visibility offset ${visibilityOffset}`);
const result = [
...list.map((item: T, index) => itemRenderer(item, index)),
inTable ? (
<tr key="pagedLoaderLastItem"><td colSpan={ colSpan }>
<VisibilitySensor offset={ { bottom: -visibilityOffset } } onChange={ this.endOfListVisibilityChange } />
{ loading ? <div>{ loadingIndicator }</div> : null }
</td></tr>
) : (
<div key="pagedLoaderLastItem">
<VisibilitySensor onChange={ this.endOfListVisibilityChange } />
{ loading ? loadingIndicator : null }
</div>
),
];
log('Done rendering');
return result;
}
}
......@@ -23,6 +23,7 @@ interface IPaginationComponentProps extends React.ClassAttributes<any> {
displayName: string;
sortBy?: string;
sortOptions?: object;
infinite?: boolean;
}
const styles = (theme) => ({
......@@ -131,8 +132,8 @@ const mobile = ['sm', 'xs'] as Breakpoint[];
class PaginationComponent extends React.Component<IPaginationComponentProps, any> {
public render() {
const {classes, displayName, width, pageObj, onChange, sortOptions} = this.props;
let {sortBy} = this.props;
const { classes, displayName, width, pageObj, onChange, sortOptions, infinite } = this.props;
let { sortBy } = this.props;
if (!sortBy) {
sortBy = pageObj && pageObj.sort ? pageObj.sort[0].property : null;
......@@ -159,7 +160,8 @@ class PaginationComponent extends React.Component<IPaginationComponentProps, any
<div className={ `${classes.floatLeft} ${classes.bold} ${classes.textPagination}` }>
<Number value={ pageObj ? pageObj.totalElements : 0 } /> { displayName || 'items' }
</div>
<Select
{ ! infinite &&
<Select
value={ pageObj ? pageObj.size : 10 }
onChange={
(e: any) => { // tslint:disable-line
......@@ -175,7 +177,7 @@ class PaginationComponent extends React.Component<IPaginationComponentProps, any
},
},
} }
>
>
{
results.map((e, i) => (
<MenuItem key={ i } value={ e } className={ classes.liItem }>
......@@ -183,7 +185,8 @@ class PaginationComponent extends React.Component<IPaginationComponentProps, any
</MenuItem>
))
}
</Select>
</Select>
}
{
sortOptions && (
<Select
......@@ -225,7 +228,8 @@ class PaginationComponent extends React.Component<IPaginationComponentProps, any
</Select>
)
}
<div className={ isMobile ? classes.floatRight : classes.floatLeft }>
{ ! infinite &&
<div className={ isMobile ? classes.floatRight : classes.floatLeft }>
<Hidden implementation="css" mdUp>
<span className={ classes.verticalAlign }>
<span className={ classes.bold }>{ pageObj ? pageObj.number + 1 : 0 }</span>/{ pageObj ? pageObj.totalPages : 0 }
......@@ -279,7 +283,8 @@ class PaginationComponent extends React.Component<IPaginationComponentProps, any
<Hidden implementation="css" only={ mobile }>
<span className={ classes.verticalAlign }>of <Number value={ pageObj ? pageObj.totalPages : 0 } /> pages</span>
</Hidden>
</div>
</div>
}
</Grid>
</Grid>
);
......
......@@ -14,6 +14,7 @@ import Markdown from 'ui/catalog/markdown';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import {Table, TableRow, TableCell} from 'ui/common/tables';
import {VocabularyLink} from 'ui/catalog/Links';
import PagedLoader from 'ui/common/PagedLoader';
import PaginationComponent from 'ui/common/pagination';
import BackButton from 'ui/common/buttons/BackButton';
......@@ -95,11 +96,21 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
}
public render() {
const {classes, uuid, vocabulary} = this.props;
const {classes, uuid, vocabulary, listVocabularyTerms} = this.props;
const {paged} = this.state;
const stillLoading: boolean = (!vocabulary || (uuid && vocabulary && vocabulary.uuid !== uuid));
const loadVocabularyTermsPage = (page: number, pageSize: number) => listVocabularyTerms(vocabulary.uuid, page, pageSize);
const renderVocabularyTerm = (term: VocabularyTerm) => (
<TableRow key={ term.code }>
<TableCell className="font-bold">{ term.code }</TableCell>
<TableCell>{ term.title }</TableCell>
<TableCell>{ term.description && <Markdown source={ term.description } /> }</TableCell>
</TableRow>
);
return (
<div>
<ContentHeaderWithButton title="Vocabulary details" buttons={ <BackButton defaultTarget="/vocabulary" /> } />
......@@ -135,6 +146,7 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
{ paged.totalElements > 50 &&
<PaginationComponent pageObj={ paged }
onChange={ this.onPaginationChange }
infinite
displayName="terms"
/>
}
......@@ -145,17 +157,17 @@ class DisplayPage extends React.Component<IDisplayPageProps, any> {
<TableCell>Description</TableCell>
</TableRow>
) }>
{ paged.content.map((term: VocabularyTerm) => (
<TableRow key={ term.code }>
<TableCell className="font-bold">{ term.code }</TableCell>
<TableCell>{ term.title }</TableCell>
<TableCell>{ term.description && <Markdown source={ term.description } /> }</TableCell>
</TableRow>
)) }
<PagedLoader
paged={ paged }
loadingIndicator={ <Loading /> }
loadPage={ loadVocabularyTermsPage }
colSpan={ 3 }
itemRenderer={ renderVocabularyTerm } />
</Table>
{ paged.totalElements > 50 &&
<PaginationComponent pageObj={ paged }
onChange={ this.onPaginationChange }
infinite
displayName="terms"
/>
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment