Skip to content

Commit

Permalink
Merge branch 'feature/pagination-integration' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
gocreating committed Oct 21, 2016
2 parents daf1773 + cca1956 commit 9668797
Show file tree
Hide file tree
Showing 16 changed files with 275 additions and 27 deletions.
17 changes: 17 additions & 0 deletions src/common/actions/pageActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ActionTypes from '../constants/ActionTypes';

export const setPage = (resourceName, page) => {
return {
type: ActionTypes.SET_PAGE,
resourceName,
page,
};
};

export const setCrrentPage = (resourceName, currentPage) => {
return {
type: ActionTypes.SET_CURRENT_PAGE,
resourceName,
currentPage,
};
};
2 changes: 1 addition & 1 deletion src/common/api/todo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default (apiEngine) => ({
// resolve(apiEngine.get('/api/todo'));
// }, 5000);
// }),
list: () => apiEngine.get('/api/todos'),
list: ({ page }) => apiEngine.get('/api/todos', { params: { page } }),
create: (todo) => apiEngine.post('/api/todos', { data: todo }),
update: (id, todo) => apiEngine.put(`/api/todos/${id}`, { data: todo }),
remove: (id) => apiEngine.del(`/api/todos/${id}`),
Expand Down
1 change: 1 addition & 0 deletions src/common/components/layouts/AppLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const AppLayout = ({ children }) => (
links={[
// jscs:disable
'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css',
'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css',
'/css/main.css',
// jscs:enable
]}
Expand Down
52 changes: 32 additions & 20 deletions src/common/components/pages/todo/ListPage.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import PageHeader from 'react-bootstrap/lib/PageHeader';
import Resources from '../../../constants/Resources';
import todoAPI from '../../../api/todo';
import { pushErrors } from '../../../actions/errorActions';
import {
setTodo,
addTodo,
removeTodo,
} from '../../../actions/todoActions';
import { setCrrentPage, setPage } from '../../../actions/pageActions';
import PageLayout from '../../layouts/PageLayout';
import Pagination from '../../utils/BsPagination';

class TodoItem extends Component {
constructor() {
Expand Down Expand Up @@ -84,25 +88,29 @@ class ListPage extends Component {
}

componentDidMount() {
let { todos } = this.props;

if (todos.length === 0) {
this.fetchTodos();
}
let { dispatch, location } = this.props;
dispatch(setCrrentPage(Resources.TODO, location.query.page || 1));
}

fetchTodos() {
let { dispatch, apiEngine } = this.props;

todoAPI(apiEngine)
.list()
.catch((err) => {
dispatch(pushErrors(err));
throw err;
})
.then((json) => {
dispatch(setTodo(json.todos));
});
componentDidUpdate(prevProps) {
let { dispatch, apiEngine, page, router, location } = this.props;

if (prevProps.page.current !== page.current) {
todoAPI(apiEngine)
.list({ page: page.current })
.catch((err) => {
dispatch(pushErrors(err));
throw err;
})
.then((json) => {
dispatch(setTodo(json.todos));
dispatch(setPage(Resources.TODO, json.page));
router.push({
pathname: location.pathname,
query: { page: json.page.current },
});
});
}
}

_handleAddClick() {
Expand Down Expand Up @@ -148,9 +156,11 @@ class ListPage extends Component {
}

render() {
let { page } = this.props;

return (
<PageLayout>
<PageHeader>Todo List</PageHeader>
<PageHeader>Todo List ({`${page.current} / ${page.total}`})</PageHeader>
<input type="text" ref="todotext" />
<button onClick={this.handleAddClick}>Add Todo</button>
<ul>
Expand All @@ -161,12 +171,14 @@ class ListPage extends Component {
onSaveClick={this.handleSaveClick.bind(this, todo._id)}
text={todo.text} />)}
</ul>
<Pagination resourceName={Resources.TODO} />
</PageLayout>
);
}
};

export default connect(state => ({
export default withRouter(connect(state => ({
apiEngine: state.apiEngine,
todos: state.todos,
}))(ListPage);
page: state.pages[Resources.TODO] || {},
}))(ListPage));
86 changes: 86 additions & 0 deletions src/common/components/utils/BsPagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import cx from 'classnames';
import { setCrrentPage } from '../../actions/pageActions';
import Text from '../widgets/Text';

let style = {
cursor: 'pointer',
margin: 2,
};

let Pagination = ({ simple, resourceName, pages, dispatch, ...rest }) => {
let page = pages[resourceName] || pages.default;

return (
<nav {...rest}>
<ul className="pager">
{!simple && (
<li
className={cx({'disabled': page.current === page.first})}
style={style}
>
<a
onClick={() =>
dispatch(setCrrentPage(resourceName, page.first))
}
>
<i className="fa fa-angle-double-left" aria-hidden="true" />
{' '}<Text id="page.first" />
</a>
</li>
)}
<li
className={cx({'disabled': page.current === page.first})}
style={style}
>
<a
onClick={() =>
dispatch(setCrrentPage(resourceName, page.current - 1))
}
>
<i className="fa fa-chevron-left" aria-hidden="true" />
{' '}<Text id="page.prev" />
</a>
</li>
<li
className={cx({'disabled': page.current === page.last})}
style={style}
>
<a
onClick={() =>
dispatch(setCrrentPage(resourceName, page.current + 1))
}
>
<Text id="page.next" />{' '}
<i className="fa fa-chevron-right" aria-hidden="true" />
</a>
</li>
{!simple && (
<li
className={cx({'disabled': page.current === page.last})}
style={style}
>
<a
onClick={() =>
dispatch(setCrrentPage(resourceName, page.last))
}
>
<Text id="page.last" />{' '}
<i className="fa fa-angle-double-right" aria-hidden="true" />
</a>
</li>
)}
</ul>
</nav>
);
};

Pagination.propTypes = {
resourceName: PropTypes.string.isRequired,
simple: PropTypes.bool,
};

export default connect(state => ({
pages: state.pages,
}))(Pagination);
3 changes: 3 additions & 0 deletions src/common/constants/ActionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ export default {

PUSH_ERRORS: 'PUSH_ERRORS',
REMOVE_ERROR: 'REMOVE_ERROR',

SET_PAGE: 'SET_PAGE',
SET_CURRENT_PAGE: 'SET_CURRENT_PAGE',
};
3 changes: 3 additions & 0 deletions src/common/constants/Resources.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
TODO: 'TODO',
};
4 changes: 4 additions & 0 deletions src/common/i18n/en-us.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ export default {
'nav.user.register': 'Register',
'nav.user.profile': 'Profile',
'nav.user.logout': 'Logout',
'page.first': 'First Page',
'page.prev': 'Previous',
'page.next': 'Next',
'page.last': 'Last Page',
};
4 changes: 4 additions & 0 deletions src/common/i18n/zh-tw.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ export default {
'nav.user.register': '註冊',
'nav.user.profile': '會員中心',
'nav.user.logout': '登出',
'page.first': '第一頁',
'page.prev': '上一頁',
'page.next': '下一頁',
'page.last': '最後一頁',
};
2 changes: 2 additions & 0 deletions src/common/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { combineReducers } from 'redux';
import cookies from './cookieReducer';
import errors from './errorReducer';
import apiEngine from './apiEngineReducer';
import pages from './pageReducer';
import todos from './todoReducer';
import form from './formReducer';
import intl from './intlReducer';
Expand All @@ -10,6 +11,7 @@ const rootReducer = combineReducers({
cookies,
errors,
apiEngine,
pages,
todos,
form, // must mount as `form` from redux-form's docs
intl,
Expand Down
39 changes: 39 additions & 0 deletions src/common/reducers/pageReducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import ActionTypes from '../constants/ActionTypes';

let defaultPage = {
skip: 0,
limit: 20,
first: 1,
current: 1,
last: 1,
total: 1,
};

let initState = {
default: defaultPage,
};

export default (state = initState, action) => {
switch (action.type) {
case ActionTypes.SET_PAGE: {
return {
...state,
[action.resourceName]: action.page || defaultPage,
};
}
case ActionTypes.SET_CURRENT_PAGE: {
let page = state[action.resourceName];

return {
...state,
[action.resourceName]: {
...page,
current: Number(action.currentPage),
},
};
}
default: {
return state;
}
}
};
2 changes: 1 addition & 1 deletion src/common/reducers/todoReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export default (state = [], action) => {
}
case ActionTypes.ADD_TODO: {
return [
...state,
action.todo,
...state,
];
}
case ActionTypes.REMOVE_TODO: {
Expand Down
5 changes: 4 additions & 1 deletion src/server/controllers/ssrFetchState.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import Errors from '../../common/constants/Errors';
import Resources from '../../common/constants/Resources';
import todoAPI from '../../common/api/todo';
import wrapTimeout from '../decorators/wrapTimeout';
import { loginUser } from '../../common/actions/userActions';
import { updateLocale } from '../../common/actions/intlActions';
import { setTodo } from '../../common/actions/todoActions';
import { setPage } from '../../common/actions/pageActions';

export default {
user: (req, res, next) => {
Expand Down Expand Up @@ -35,7 +37,7 @@ export default {
}),
todo: wrapTimeout(3000)((req, res, next) => {
todoAPI(req.store.getState().apiEngine)
.list()
.list({ page: req.query.page || 1 })
.catch(() => {
res.pushError(Errors.STATE_PRE_FETCHING_FAIL, {
detail: 'Cannot list todos',
Expand All @@ -44,6 +46,7 @@ export default {
})
.then((json) => {
req.store.dispatch(setTodo(json.todos));
req.store.dispatch(setPage(Resources.TODO, json.page));
next();
});
}),
Expand Down
19 changes: 15 additions & 4 deletions src/server/controllers/todo.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@ import Todo from '../models/Todo';

export default {
list(req, res) {
Todo.find({}, handleDbError(res)((todos) => {
res.json({
todos: todos,
});
Todo.paginate({
page: req.query.page,
perPage: 5,
}, handleDbError(res)((page) => {
Todo
.find({})
.sort({ createdAt: 'desc' })
.limit(page.limit)
.skip(page.skip)
.exec(handleDbError(res)((todos) => {
res.json({
todos: todos,
page: page,
});
}));
}));
},

Expand Down
3 changes: 3 additions & 0 deletions src/server/models/Todo.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import mongoose from 'mongoose';
import paginatePlugin from './plugins/paginate';

let Todo = new mongoose.Schema({
text: String,
Expand All @@ -10,4 +11,6 @@ let Todo = new mongoose.Schema({
},
});

Todo.plugin(paginatePlugin);

export default mongoose.model('Todo', Todo);
Loading

0 comments on commit 9668797

Please sign in to comment.