mirror of
https://github.com/strapi/strapi.git
synced 2026-01-07 20:58:16 +00:00
Add pagination in list view
This commit is contained in:
parent
3876aefd03
commit
fedc7dd1e4
@ -16,6 +16,14 @@
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/explorer/:model/count",
|
||||
"handler": "ContentManager.count",
|
||||
"config": {
|
||||
"policies": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/explorer/:model/:id",
|
||||
|
||||
@ -16,19 +16,37 @@ module.exports = {
|
||||
find: async(ctx) => {
|
||||
const model = ctx.params.model;
|
||||
|
||||
const {
|
||||
limit = 10,
|
||||
skip = 0,
|
||||
} = ctx.request.query;
|
||||
|
||||
const entries = await User
|
||||
.find();
|
||||
.find()
|
||||
.limit(limit)
|
||||
.limit(skip);
|
||||
|
||||
ctx.body = entries;
|
||||
},
|
||||
|
||||
count: async(ctx) => {
|
||||
const model = ctx.params.model;
|
||||
|
||||
const count = await User
|
||||
.count();
|
||||
|
||||
ctx.body = {
|
||||
count: Number(count)
|
||||
};
|
||||
},
|
||||
|
||||
findOne: async(ctx) => {
|
||||
const model = ctx.params.model;
|
||||
const id = ctx.params.id;
|
||||
|
||||
const entries = await User
|
||||
.find({
|
||||
id: id
|
||||
id
|
||||
});
|
||||
|
||||
ctx.body = entries;
|
||||
|
||||
@ -0,0 +1,180 @@
|
||||
/**
|
||||
*
|
||||
* Pagination
|
||||
*
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class Pagination extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
progress: -1,
|
||||
};
|
||||
this.onGoPreviousPageClicked = this.onGoPreviousPageClicked.bind(this);
|
||||
this.onGoNextPageClicked = this.onGoNextPageClicked.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is the first one or not
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFirstPage() {
|
||||
return this.props.currentPage === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
needPreviousLinksDots() {
|
||||
return this.props.currentPage >= 3;
|
||||
}
|
||||
|
||||
needAfterLinksDots() {
|
||||
return this.props.currentPage < this.lastPage() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the last page number
|
||||
*
|
||||
* @returns {number}
|
||||
*/
|
||||
lastPage() {
|
||||
return Math.ceil(this.props.count / this.props.limitPerPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current page is the last one
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isLastPage() {
|
||||
return this.props.currentPage === this.lastPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered on previous page click.
|
||||
*
|
||||
* Prevent link default behavior and go to previous page.
|
||||
*
|
||||
* @param e {Object} Click event
|
||||
*/
|
||||
onGoPreviousPageClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.isFirstPage()) {
|
||||
this.props.goPreviousPage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered on next page click.
|
||||
*
|
||||
* Prevent link default behavior and go to next page.
|
||||
*
|
||||
* @param e {Object} Click event
|
||||
*/
|
||||
onGoNextPageClicked(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!this.isLastPage()) {
|
||||
this.props.goNextPage();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// Init variables
|
||||
let beforeLinksDots;
|
||||
let afterLinksDots;
|
||||
const linksOptions = [];
|
||||
const dotsElem = (<li className={styles.navLi}><span>...</span></li>);
|
||||
|
||||
// Add active page link
|
||||
linksOptions.push({
|
||||
value: this.props.currentPage,
|
||||
isActive: true,
|
||||
onClick: (e) => {
|
||||
e.preventDefault();
|
||||
},
|
||||
});
|
||||
|
||||
// Add previous page link
|
||||
if (!this.isFirstPage()) {
|
||||
linksOptions.unshift({
|
||||
value: this.props.currentPage - 1,
|
||||
isActive: false,
|
||||
onClick: this.onGoPreviousPageClicked,
|
||||
});
|
||||
}
|
||||
|
||||
// Add next page link
|
||||
if (!this.isLastPage()) {
|
||||
linksOptions.push({
|
||||
value: this.props.currentPage + 1,
|
||||
isActive: false,
|
||||
onClick: this.onGoNextPageClicked,
|
||||
});
|
||||
}
|
||||
|
||||
// Add previous link dots
|
||||
if (this.needPreviousLinksDots()) {
|
||||
beforeLinksDots = dotsElem;
|
||||
}
|
||||
|
||||
// Add next link dots
|
||||
if (this.needAfterLinksDots()) {
|
||||
afterLinksDots = dotsElem;
|
||||
}
|
||||
|
||||
// Generate links
|
||||
const links = linksOptions.map((linksOption) => (
|
||||
<li className={`${styles.navLi} ${linksOption.isActive && styles.navLiActive}`} key={linksOption.value}>
|
||||
<a href disabled={linksOption.isActive} onClick={linksOption.onClick}>
|
||||
{linksOption.value}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.pagination}>
|
||||
<a href
|
||||
className={`
|
||||
${styles.paginationNavigator}
|
||||
${styles.paginationNavigatorPrevious}
|
||||
${this.isFirstPage() && styles.paginationNavigatorDisabled}
|
||||
`}
|
||||
onClick={this.onGoPreviousPageClicked}
|
||||
disabled={this.isFirstPage()}>
|
||||
<i className="ion ion-chevron-left"></i>
|
||||
</a>
|
||||
<div className={styles.separator}></div>
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.navUl}>
|
||||
{ beforeLinksDots }
|
||||
{links}
|
||||
{ afterLinksDots }
|
||||
</ul>
|
||||
</nav>
|
||||
<a href
|
||||
className={`
|
||||
${styles.paginationNavigator}
|
||||
${styles.paginationNavigatorNext}
|
||||
${this.isLastPage() && styles.paginationNavigatorDisabled}
|
||||
`}
|
||||
onClick={this.onGoNextPageClicked}
|
||||
disabled={this.isLastPage()}>
|
||||
<i className="ion ion-chevron-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Pagination;
|
||||
@ -0,0 +1,78 @@
|
||||
.pagination {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
margin-top: 2rem;
|
||||
border: 1px solid #DADBDC;
|
||||
border-radius: 2.4rem;
|
||||
}
|
||||
|
||||
.paginationNavigator {
|
||||
background: #FFFFFF;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
border-right: 1px solid #EFF3F6;
|
||||
}
|
||||
|
||||
.paginationNavigatorDisabled {
|
||||
color: #DADBDC;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: #DADBDC;
|
||||
}
|
||||
}
|
||||
|
||||
.paginationNavigatorPrevious {
|
||||
border-radius: 2.4rem 0 0 2.4rem;
|
||||
border-right: 1px solid #EFF3F6;
|
||||
}
|
||||
|
||||
.paginationNavigatorNext {
|
||||
border-radius: 0 2.4rem 2.4rem 0;
|
||||
border-left: 1px solid #EFF3F6;
|
||||
}
|
||||
|
||||
.nav {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
.navUl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.navLi {
|
||||
padding-top: 1rem;
|
||||
padding-left: 0.8rem;
|
||||
padding-right: 0.8rem;
|
||||
color: #465373;
|
||||
border-bottom: 3px solid transparent;
|
||||
|
||||
a,
|
||||
a:visited,
|
||||
a:focus,
|
||||
a:active {
|
||||
text-decoration: none;
|
||||
color: #465373;;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #215CEA;
|
||||
}
|
||||
}
|
||||
|
||||
.navLiActive {
|
||||
border-bottom: 3px solid #215CEA;
|
||||
|
||||
a,
|
||||
a:hover{
|
||||
color: #000000 !important;
|
||||
}
|
||||
}
|
||||
@ -9,8 +9,6 @@ import React from 'react';
|
||||
import TableHeader from 'components/TableHeader';
|
||||
import TableRow from 'components/TableRow';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import messages from './messages';
|
||||
import styles from './styles.scss';
|
||||
|
||||
class Table extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Table Messages
|
||||
*
|
||||
* This contains all the text for the Table component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export default defineMessages({
|
||||
header: {
|
||||
id: 'app.components.Table.header',
|
||||
defaultMessage: 'This is the Table component !',
|
||||
},
|
||||
});
|
||||
@ -1,11 +0,0 @@
|
||||
// import Table from '../index';
|
||||
|
||||
import expect from 'expect';
|
||||
// import { shallow } from 'enzyme';
|
||||
// import React from 'react';
|
||||
|
||||
describe('<Table />', () => {
|
||||
it('Expect to have unit tests specified', () => {
|
||||
expect(true).toEqual(false);
|
||||
});
|
||||
});
|
||||
@ -6,15 +6,11 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import messages from './messages';
|
||||
import styles from './styles.scss';
|
||||
|
||||
class TableHeader extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
const headers = this.props.headers.map((header, i) => {
|
||||
return <th key={i}>{header.label}</th>
|
||||
});
|
||||
const headers = this.props.headers.map((header, i) => (<th key={i}>{header.label}</th>));
|
||||
|
||||
return (
|
||||
<thead className={styles.tableHeader}>
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* TableHeader Messages
|
||||
*
|
||||
* This contains all the text for the TableHeader component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export default defineMessages({
|
||||
header: {
|
||||
id: 'app.components.TableHeader.header',
|
||||
defaultMessage: 'This is the TableHeader component !',
|
||||
},
|
||||
});
|
||||
@ -1,11 +0,0 @@
|
||||
// import TableHeader from '../index';
|
||||
|
||||
import expect from 'expect';
|
||||
// import { shallow } from 'enzyme';
|
||||
// import React from 'react';
|
||||
|
||||
describe('<TableHeader />', () => {
|
||||
it('Expect to have unit tests specified', () => {
|
||||
expect(true).toEqual(false);
|
||||
});
|
||||
});
|
||||
@ -5,17 +5,13 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Link } from 'react-router';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import messages from './messages';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
class TableRow extends React.Component { // eslint-disable-line react/prefer-stateless-function
|
||||
render() {
|
||||
const cells = this.props.headers.map((header, i) => {
|
||||
return <td key={i} className={styles.tableRowCell}>{this.props.record[header.name]}</td>
|
||||
});
|
||||
const cells = this.props.headers.map((header, i) => (<td key={i} className={styles.tableRowCell}>{this.props.record[header.name]}</td>));
|
||||
|
||||
return (
|
||||
<tr className={styles.tableRow}>
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
/*
|
||||
* TableRow Messages
|
||||
*
|
||||
* This contains all the text for the TableRow component.
|
||||
*/
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
export default defineMessages({
|
||||
header: {
|
||||
id: 'app.components.TableRow.header',
|
||||
defaultMessage: 'This is the TableRow component !',
|
||||
},
|
||||
});
|
||||
@ -1,11 +0,0 @@
|
||||
// import TableRow from '../index';
|
||||
|
||||
import expect from 'expect';
|
||||
// import { shallow } from 'enzyme';
|
||||
// import React from 'react';
|
||||
|
||||
describe('<TableRow />', () => {
|
||||
it('Expect to have unit tests specified', () => {
|
||||
expect(true).toEqual(false);
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,11 @@
|
||||
import {
|
||||
SET_CURRENT_MODEL_NAME,
|
||||
LOAD_RECORDS,
|
||||
LOADED_RECORDS
|
||||
LOADED_RECORDS,
|
||||
LOAD_COUNT,
|
||||
LOADED_COUNT,
|
||||
GO_NEXT_PAGE,
|
||||
GO_PREVIOUS_PAGE,
|
||||
} from './constants';
|
||||
|
||||
export function setCurrentModelName(modelName) {
|
||||
@ -29,3 +33,28 @@ export function loadedRecord(records) {
|
||||
records,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadCount() {
|
||||
return {
|
||||
type: LOAD_COUNT,
|
||||
};
|
||||
}
|
||||
|
||||
export function loadedCount(count) {
|
||||
return {
|
||||
type: LOADED_COUNT,
|
||||
count,
|
||||
};
|
||||
}
|
||||
|
||||
export function goNextPage() {
|
||||
return {
|
||||
type: GO_NEXT_PAGE,
|
||||
};
|
||||
}
|
||||
|
||||
export function goPreviousPage() {
|
||||
return {
|
||||
type: GO_PREVIOUS_PAGE,
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,5 +5,12 @@
|
||||
*/
|
||||
|
||||
export const SET_CURRENT_MODEL_NAME = 'app/List/SET_CURRENT_MODEL_NAME';
|
||||
|
||||
export const LOAD_RECORDS = 'app/List/LOAD_RECORDS';
|
||||
export const LOADED_RECORDS = 'app/List/LOADED_RECORDS';
|
||||
|
||||
export const LOAD_COUNT = 'app/List/LOAD_COUNT';
|
||||
export const LOADED_COUNT = 'app/List/LOADED_COUNT';
|
||||
|
||||
export const GO_NEXT_PAGE = 'app/List/GO_NEXT_PAGE';
|
||||
export const GO_PREVIOUS_PAGE = 'app/List/GO_PREVIOUS_PAGE';
|
||||
|
||||
@ -8,21 +8,30 @@ import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import _ from 'lodash';
|
||||
|
||||
import Container from 'components/Container';
|
||||
import Table from 'components/Table';
|
||||
import _ from 'lodash';
|
||||
import Pagination from 'components/Pagination';
|
||||
|
||||
import styles from './styles.scss';
|
||||
|
||||
import {
|
||||
setCurrentModelName,
|
||||
loadRecords,
|
||||
loadCount,
|
||||
goNextPage,
|
||||
goPreviousPage,
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
makeSelectLoading,
|
||||
makeSelectModelRecords,
|
||||
makeSelectRecords,
|
||||
makeSelectLoadingRecords,
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectCount,
|
||||
makeSelectCurrentPage,
|
||||
makeSelectLimitPerPage,
|
||||
makeSelectLoadingCount,
|
||||
} from './selectors';
|
||||
|
||||
import {
|
||||
@ -33,13 +42,14 @@ export class List extends React.Component { // eslint-disable-line react/prefer-
|
||||
componentWillMount() {
|
||||
this.props.setCurrentModelName(this.props.routeParams.slug.toLowerCase());
|
||||
this.props.loadRecords();
|
||||
this.props.loadCount();
|
||||
}
|
||||
|
||||
render() {
|
||||
const PluginHeader = this.props.exposedComponents.PluginHeader;
|
||||
|
||||
let content;
|
||||
if (this.props.loading) {
|
||||
if (this.props.loadingRecords) {
|
||||
content = (
|
||||
<div>
|
||||
<p>Loading...</p>
|
||||
@ -54,11 +64,10 @@ export class List extends React.Component { // eslint-disable-line react/prefer-
|
||||
|
||||
// Define table headers
|
||||
const tableHeaders = _.map(displayedAttributes, (value, key) => ({
|
||||
name: key,
|
||||
label: key,
|
||||
type: value.type,
|
||||
})
|
||||
);
|
||||
name: key,
|
||||
label: key,
|
||||
type: value.type,
|
||||
}));
|
||||
|
||||
content = (
|
||||
<Table
|
||||
@ -67,7 +76,7 @@ export class List extends React.Component { // eslint-disable-line react/prefer-
|
||||
routeParams={this.props.routeParams}
|
||||
headers={tableHeaders}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@ -84,6 +93,13 @@ export class List extends React.Component { // eslint-disable-line react/prefer-
|
||||
<Container>
|
||||
<p></p>
|
||||
{content}
|
||||
<Pagination
|
||||
limitPerPage={this.props.limitPerPage}
|
||||
currentPage={this.props.currentPage}
|
||||
goNextPage={this.props.goNextPage}
|
||||
goPreviousPage={this.props.goPreviousPage}
|
||||
count={this.props.count}
|
||||
/>
|
||||
</Container>
|
||||
</div>
|
||||
</div>
|
||||
@ -98,26 +114,45 @@ List.propTypes = {
|
||||
React.PropTypes.bool,
|
||||
]),
|
||||
loadRecords: React.PropTypes.func,
|
||||
loading: React.PropTypes.bool,
|
||||
loadingRecords: React.PropTypes.bool,
|
||||
models: React.PropTypes.oneOfType([
|
||||
React.PropTypes.object,
|
||||
React.PropTypes.bool,
|
||||
]),
|
||||
currentPage: React.PropTypes.number,
|
||||
limitPerPage: React.PropTypes.number,
|
||||
currentModelName: React.PropTypes.string,
|
||||
goNextPage: React.PropTypes.func,
|
||||
goPreviousPage: React.PropTypes.func,
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
setCurrentModelName: (modelName) => dispatch(setCurrentModelName(modelName)),
|
||||
loadRecords: () => dispatch(loadRecords()),
|
||||
loadCount: () => dispatch(loadCount()),
|
||||
goNextPage: () => {
|
||||
dispatch(goNextPage());
|
||||
dispatch(loadRecords());
|
||||
dispatch(loadCount());
|
||||
},
|
||||
goPreviousPage: () => {
|
||||
dispatch(goPreviousPage());
|
||||
dispatch(loadRecords());
|
||||
dispatch(loadCount());
|
||||
},
|
||||
dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
records: makeSelectModelRecords(),
|
||||
loading: makeSelectLoading(),
|
||||
records: makeSelectRecords(),
|
||||
loadingRecords: makeSelectLoadingRecords(),
|
||||
count: makeSelectCount(),
|
||||
loadingCount: makeSelectLoadingCount(),
|
||||
models: makeSelectModels(),
|
||||
currentPage: makeSelectCurrentPage(),
|
||||
limitPerPage: makeSelectLimitPerPage(),
|
||||
currentModelName: makeSelectCurrentModelName(),
|
||||
});
|
||||
|
||||
|
||||
@ -8,13 +8,21 @@ import { fromJS } from 'immutable';
|
||||
import {
|
||||
SET_CURRENT_MODEL_NAME,
|
||||
LOAD_RECORDS,
|
||||
LOADED_RECORDS
|
||||
LOADED_RECORDS,
|
||||
LOAD_COUNT,
|
||||
LOADED_COUNT,
|
||||
GO_NEXT_PAGE,
|
||||
GO_PREVIOUS_PAGE,
|
||||
} from './constants';
|
||||
|
||||
const initialState = fromJS({
|
||||
currentModel: null,
|
||||
loading: true,
|
||||
loadingRecords: true,
|
||||
records: false,
|
||||
loadingCount: true,
|
||||
count: false,
|
||||
currentPage: 1,
|
||||
limitPerPage: 10,
|
||||
});
|
||||
|
||||
function listReducer(state = initialState, action) {
|
||||
@ -24,11 +32,24 @@ function listReducer(state = initialState, action) {
|
||||
.set('currentModelName', action.modelName);
|
||||
case LOAD_RECORDS:
|
||||
return state
|
||||
.set('loading', true);
|
||||
.set('loadingRecords', true);
|
||||
case LOADED_RECORDS:
|
||||
return state
|
||||
.set('loading', false)
|
||||
.set('loadingRecords', false)
|
||||
.set('records', action.records);
|
||||
case LOAD_COUNT:
|
||||
return state
|
||||
.set('loadingCount', true);
|
||||
case LOADED_COUNT:
|
||||
return state
|
||||
.set('loadingCount', false)
|
||||
.set('count', action.count);
|
||||
case GO_NEXT_PAGE:
|
||||
return state
|
||||
.set('currentPage', state.get('currentPage') + 1);
|
||||
case GO_PREVIOUS_PAGE:
|
||||
return state
|
||||
.set('currentPage', state.get('currentPage') - 1);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@ -1,20 +1,54 @@
|
||||
import { takeLatest } from 'redux-saga';
|
||||
import { put, select } from 'redux-saga/effects';
|
||||
import { put, select, fork, call } from 'redux-saga/effects';
|
||||
import request from 'utils/request';
|
||||
|
||||
import {
|
||||
loadedRecord
|
||||
loadedRecord,
|
||||
loadedCount,
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
LOAD_RECORDS
|
||||
LOAD_RECORDS,
|
||||
LOAD_COUNT,
|
||||
} from './constants';
|
||||
|
||||
import {
|
||||
makeSelectCurrentModelName,
|
||||
makeSelectLimitPerPage,
|
||||
makeSelectCurrentPage,
|
||||
} from './selectors';
|
||||
|
||||
export function* getRecords() {
|
||||
const currentModel = yield select(makeSelectCurrentModelName());
|
||||
const limitPerPage = yield select(makeSelectLimitPerPage());
|
||||
const currentPage = yield select(makeSelectCurrentPage());
|
||||
|
||||
// Calculate the number of values to be skip
|
||||
const skip = (currentPage - 1) * limitPerPage;
|
||||
|
||||
// Init `params` object
|
||||
const params = {
|
||||
skip,
|
||||
limit: limitPerPage,
|
||||
};
|
||||
|
||||
try {
|
||||
const requestURL = `http://localhost:1337/content-manager/explorer/${currentModel}`;
|
||||
|
||||
// Call our request helper (see 'utils/request')
|
||||
const data = yield call(request, requestURL, {
|
||||
method: 'GET',
|
||||
params,
|
||||
});
|
||||
|
||||
yield put(loadedRecord(data));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
export function* getCount() {
|
||||
const currentModel = yield select(makeSelectCurrentModelName());
|
||||
|
||||
try {
|
||||
const opts = {
|
||||
@ -22,19 +56,20 @@ export function* getRecords() {
|
||||
mode: 'cors',
|
||||
cache: 'default'
|
||||
};
|
||||
const response = yield fetch(`http://localhost:1337/content-manager/explorer/${currentModel}`, opts);
|
||||
const response = yield fetch(`http://localhost:1337/content-manager/explorer/${currentModel}/count`, opts);
|
||||
|
||||
const data = yield response.json();
|
||||
|
||||
yield put(loadedRecord(data));
|
||||
yield put(loadedCount(data.count));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Individual exports for testing
|
||||
export function* defaultSaga() {
|
||||
yield takeLatest(LOAD_RECORDS, getRecords);
|
||||
yield fork(takeLatest, LOAD_RECORDS, getRecords);
|
||||
yield fork(takeLatest, LOAD_COUNT, getCount);
|
||||
}
|
||||
|
||||
// All sagas to be loaded
|
||||
|
||||
@ -14,16 +14,38 @@ const selectListDomain = () => state => state.get('list');
|
||||
* Default selector used by List
|
||||
*/
|
||||
|
||||
const makeSelectModelRecords = () => createSelector(
|
||||
const makeSelectRecords = () => createSelector(
|
||||
selectListDomain(),
|
||||
(substate) => {
|
||||
return substate.get('records');
|
||||
}
|
||||
);
|
||||
|
||||
const makeSelectLoading = () => createSelector(
|
||||
const makeSelectLoadingRecords = () => createSelector(
|
||||
selectListDomain(),
|
||||
(substate) => substate.get('loading')
|
||||
(substate) => substate.get('loadingRecords')
|
||||
);
|
||||
|
||||
const makeSelectCount = () => createSelector(
|
||||
selectListDomain(),
|
||||
(substate) => {
|
||||
return substate.get('count');
|
||||
}
|
||||
);
|
||||
|
||||
const makeSelectLoadingCount = () => createSelector(
|
||||
selectListDomain(),
|
||||
(substate) => substate.get('loadingCount')
|
||||
);
|
||||
|
||||
const makeSelectCurrentPage = () => createSelector(
|
||||
selectListDomain(),
|
||||
(substate) => substate.get('currentPage')
|
||||
);
|
||||
|
||||
const makeSelectLimitPerPage = () => createSelector(
|
||||
selectListDomain(),
|
||||
(substate) => substate.get('limitPerPage')
|
||||
);
|
||||
|
||||
const makeSelectCurrentModelName = () => createSelector(
|
||||
@ -33,7 +55,11 @@ const makeSelectCurrentModelName = () => createSelector(
|
||||
|
||||
export {
|
||||
selectListDomain,
|
||||
makeSelectLoading,
|
||||
makeSelectModelRecords,
|
||||
makeSelectRecords,
|
||||
makeSelectLoadingRecords,
|
||||
makeSelectCount,
|
||||
makeSelectLoadingCount,
|
||||
makeSelectCurrentPage,
|
||||
makeSelectLimitPerPage,
|
||||
makeSelectCurrentModelName,
|
||||
};
|
||||
|
||||
@ -46,7 +46,7 @@ export class Single extends React.Component { // eslint-disable-line react/prefe
|
||||
<ul>
|
||||
{items}
|
||||
</ul>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -16,22 +16,32 @@ function parseJSON(response) {
|
||||
*
|
||||
* @param {object} response A response from a network request
|
||||
*
|
||||
* @return {Promise} Returns either the response, or throws an error
|
||||
* @return {object|undefined} Returns either the response, or throws an error
|
||||
*/
|
||||
function checkStatus(response) {
|
||||
return new Promise((resolve) => {
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return resolve(response);
|
||||
}
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return parseJSON(response)
|
||||
.then((data) => {
|
||||
const error = new Error(data.message || response.statusText);
|
||||
error.data = data;
|
||||
error.response = response;
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
return parseJSON(response)
|
||||
.then((responseFormatted) => {
|
||||
const error = new Error(response.statusText);
|
||||
error.response = response;
|
||||
error.response.payload = responseFormatted;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Format query params
|
||||
*
|
||||
* @param params
|
||||
* @returns {string}
|
||||
*/
|
||||
function formatQueryParams(params) {
|
||||
return Object.keys(params)
|
||||
.map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,20 +50,29 @@ function checkStatus(response) {
|
||||
* @param {string} url The URL we want to request
|
||||
* @param {object} [options] The options we want to pass to "fetch"
|
||||
*
|
||||
* @return {object} An object containing either "data" or "err"
|
||||
* @return {object} The response data
|
||||
*/
|
||||
export default function request(url, options) {
|
||||
// Default headers
|
||||
const params = options || { };
|
||||
const defaultHeaders = {
|
||||
Accept: 'application/json',
|
||||
const optionsObj = options || {};
|
||||
|
||||
// Set headers
|
||||
optionsObj.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
params.headers = params && params.headers ? params.headers : defaultHeaders;
|
||||
|
||||
return fetch(url, params)
|
||||
// Add parameters to url
|
||||
let urlFormatted = url;
|
||||
if (optionsObj && optionsObj.params) {
|
||||
const params = formatQueryParams(optionsObj.params);
|
||||
urlFormatted = `${url}?${params}`;
|
||||
}
|
||||
|
||||
// Stringify body object
|
||||
if (optionsObj && optionsObj.body) {
|
||||
optionsObj.body = JSON.stringify(optionsObj.body);
|
||||
}
|
||||
|
||||
return fetch(urlFormatted, optionsObj)
|
||||
.then(checkStatus)
|
||||
.then(parseJSON)
|
||||
.then((data) => ({ data }))
|
||||
.catch((err) => ({ err }));
|
||||
.then(parseJSON);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user