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

Convert ServerSidePaging to React #907

Merged
merged 4 commits into from
Jul 9, 2021
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
2 changes: 1 addition & 1 deletion VocaDbWeb/Scripts/Bootstrap/PageItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const PageItem: BsPrefixRefForwardingComponent<
}: PageItemProps,
ref,
) => {
const Component = active || disabled ? 'span' : SafeAnchor;
const Component = SafeAnchor;
return (
<li
ref={ref}
Expand Down
25 changes: 25 additions & 0 deletions VocaDbWeb/Scripts/Components/Shared/Partials/EntryCountBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ServerSidePagingStore from '@Stores/ServerSidePagingStore';
import { observer } from 'mobx-react-lite';
import React from 'react';

import EntryCount from './Knockout/EntryCount';

interface EntryCountBoxProps {
pagingStore: ServerSidePagingStore;
selections?: number[];
}

const EntryCountBox = observer(
({
pagingStore,
selections = [10, 20, 40, 100],
}: EntryCountBoxProps): React.ReactElement => {
return (
<div className="pull-right">
<EntryCount pagingStore={pagingStore} selections={selections} />
</div>
);
},
);

export default EntryCountBox;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Dropdown from '@Bootstrap/Dropdown';
import SafeAnchor from '@Bootstrap/SafeAnchor';
import ServerSidePagingStore from '@Stores/ServerSidePagingStore';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { useTranslation } from 'react-i18next';

interface EntryCountProps {
pagingStore: ServerSidePagingStore;
selections?: number[];
}

const EntryCount = observer(
({
pagingStore,
selections = [10, 20, 40, 100],
}: EntryCountProps): React.ReactElement => {
const { t } = useTranslation(['ViewRes.Search']);

return (
<Dropdown>
<Dropdown.Toggle as={SafeAnchor}>
{t('ViewRes.Search:Index.ShowingItemsOf', {
0: pagingStore.pageSize,
1: pagingStore.totalItems,
})}
</Dropdown.Toggle>
<Dropdown.Menu>
{selections.map((selection) => (
<Dropdown.Item
onClick={(): void => pagingStore.setPageSize(selection)}
key={selection}
>
{t('ViewRes.Search:Index.ItemsPerPage', { 0: selection })}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
);
},
);

export default EntryCount;
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Pagination from '@Bootstrap/Pagination';
import ServerSidePagingStore from '@Stores/ServerSidePagingStore';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { useTranslation } from 'react-i18next';

interface ServerSidePagingProps {
pagingStore: ServerSidePagingStore;
}

const ServerSidePaging = observer(
({ pagingStore }: ServerSidePagingProps): React.ReactElement => {
const { t } = useTranslation(['VocaDb.Web.Resources.Other']);

return (
<Pagination>
<Pagination.First
disabled={pagingStore.isFirstPage}
onClick={pagingStore.goToFirstPage}
>
&laquo;&laquo; {t('VocaDb.Web.Resources.Other:PagedList.First')}
</Pagination.First>
<Pagination.Prev
disabled={pagingStore.isFirstPage}
onClick={pagingStore.previousPage}
>
&laquo; {t('VocaDb.Web.Resources.Other:PagedList.Previous')}
</Pagination.Prev>

{pagingStore.showMoreBegin && <Pagination.Ellipsis disabled />}

{pagingStore.pages.map((page) => (
<Pagination.Item
active={page === pagingStore.page}
onClick={(): void => pagingStore.setPage(page)}
key={page}
>
{page}
</Pagination.Item>
))}

{pagingStore.showMoreEnd && <Pagination.Ellipsis disabled />}

<Pagination.Next
disabled={pagingStore.isLastPage}
onClick={pagingStore.nextPage}
>
{t('VocaDb.Web.Resources.Other:PagedList.Next')} &raquo;
</Pagination.Next>
<Pagination.Last
disabled={pagingStore.isLastPage}
onClick={pagingStore.goToLastPage}
>
{t('VocaDb.Web.Resources.Other:PagedList.Last')} &raquo;&raquo;
</Pagination.Last>
</Pagination>
);
},
);

export default ServerSidePaging;
87 changes: 87 additions & 0 deletions VocaDbWeb/Scripts/Stores/ServerSidePagingStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import PagingProperties from '@DataContracts/PagingPropertiesContract';
import _ from 'lodash';
import { action, computed, makeObservable, observable } from 'mobx';

export default class ServerSidePagingStore {
@observable public page = 1;
@action public setPage = (value: number): void => {
this.page = value;
};

@observable public totalItems = 0;
@action public setTotalItems = (value: number): void => {
this.totalItems = value;
};

@observable public pageSize = 10;
@action public setPageSize = (value: number): void => {
this.pageSize = value;
};

public constructor(pageSize: number = 10) {
makeObservable(this);

this.pageSize = pageSize;
}

@computed public get firstItem(): number {
return (this.page - 1) * this.pageSize;
}

@computed public get totalPages(): number {
return Math.ceil(this.totalItems / this.pageSize);
}

@computed public get hasMultiplePages(): boolean {
return this.totalPages > 1;
}

@computed public get isFirstPage(): boolean {
return this.page <= 1;
}

@computed public get isLastPage(): boolean {
return this.page >= this.totalPages;
}

@computed public get pages(): number[] {
const start = Math.max(this.page - 4, 1);
const end = Math.min(this.page + 4, this.totalPages);

return _.range(start, end + 1);
}

@computed public get showMoreBegin(): boolean {
return this.page > 5;
}

@computed public get showMoreEnd(): boolean {
return this.page < this.totalPages - 4;
}

public getPagingProperties = (
clearResults: boolean = false,
): PagingProperties => {
return {
start: this.firstItem,
maxEntries: this.pageSize,
getTotalCount: clearResults || this.totalItems === 0,
};
};

@action public goToFirstPage = (): void => {
this.page = 1;
};

@action public goToLastPage = (): void => {
this.page = this.totalPages;
};

@action public nextPage = (): void => {
if (!this.isLastPage) this.page = this.page + 1;
};

@action public previousPage = (): void => {
if (!this.isFirstPage) this.page = this.page - 1;
};
}
43 changes: 43 additions & 0 deletions VocaDbWeb/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions VocaDbWeb/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
"knockout-punches": "^0.5.1",
"lodash": "^4.17.19",
"marked": "^0.3.18",
"mobx": "^6.3.2",
"mobx-react-lite": "^3.2.0",
"moment": "^2.17.0",
"qtip2": "^3.0.3",
"react": "^17.0.2",
Expand Down
5 changes: 4 additions & 1 deletion VocaDbWeb/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
"@Models/*": ["Scripts/Models/*"],
"@Repositories/*": ["Scripts/Repositories/*"],
"@Shared/*": ["Scripts/Shared/*"],
"@Stores/*": ["Scripts/Stores/*"],
"@ViewModels/*": ["Scripts/ViewModels/*"]
},
"resolveJsonModule": true
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["Scripts/**/*"]
}
1 change: 1 addition & 0 deletions VocaDbWeb/webpack.mix.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ mix
'@Models': path.join(__dirname, 'Scripts/Models'),
'@Repositories': path.join(__dirname, 'Scripts/Repositories'),
'@Shared': path.join(__dirname, 'Scripts/Shared'),
'@Stores': path.join(__dirname, 'Scripts/Stores'),
'@ViewModels': path.join(__dirname, 'Scripts/ViewModels'),
})
.eslint({
Expand Down