From ec74ab6def3309b3044a20c71487e38c1493a34d Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Sat, 26 Nov 2016 10:27:54 -0500 Subject: [PATCH] Pagination working --- app/static/app/js/Dashboard.jsx | 2 +- .../app/js/components/EditProjectDialog.jsx | 3 +- app/static/app/js/components/Map.jsx | 2 +- app/static/app/js/components/Paginated.jsx | 62 +++++++++++++++++-- app/static/app/js/components/Paginator.jsx | 6 +- app/static/app/js/components/ProjectList.jsx | 47 +++++++++----- app/static/app/js/css/ProjectList.scss | 6 ++ 7 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 app/static/app/js/css/ProjectList.scss diff --git a/app/static/app/js/Dashboard.jsx b/app/static/app/js/Dashboard.jsx index ce124628..91b043bd 100644 --- a/app/static/app/js/Dashboard.jsx +++ b/app/static/app/js/Dashboard.jsx @@ -48,7 +48,7 @@ class Dashboard extends React.Component { ref={(domNode) => { this.projectDialog = domNode; }} /> { this.projectList = domNode; }} /> ); diff --git a/app/static/app/js/components/EditProjectDialog.jsx b/app/static/app/js/components/EditProjectDialog.jsx index 46054f01..59d00652 100644 --- a/app/static/app/js/components/EditProjectDialog.jsx +++ b/app/static/app/js/components/EditProjectDialog.jsx @@ -60,7 +60,8 @@ class EditProjectDialog extends React.Component { } componentWillUnmount(){ - $(this.modal).off('hidden.bs.modal hidden.bs.modal'); + $(this.modal).off('hidden.bs.modal hidden.bs.modal') + .modal('hide'); } componentDidUpdate(){ diff --git a/app/static/app/js/components/Map.jsx b/app/static/app/js/components/Map.jsx index 7cd5369e..5eeb3019 100644 --- a/app/static/app/js/components/Map.jsx +++ b/app/static/app/js/components/Map.jsx @@ -2,7 +2,7 @@ import React from 'react'; import '../css/Map.scss'; import 'leaflet/dist/leaflet.css'; import Leaflet from 'leaflet'; -import async from 'async/dist/async'; +import async from 'async'; import 'leaflet-measure/dist/leaflet-measure.css'; import 'leaflet-measure/dist/leaflet-measure'; import '../vendor/leaflet/L.Control.MousePosition.css'; diff --git a/app/static/app/js/components/Paginated.jsx b/app/static/app/js/components/Paginated.jsx index 889673c1..43d98391 100644 --- a/app/static/app/js/components/Paginated.jsx +++ b/app/static/app/js/components/Paginated.jsx @@ -1,28 +1,80 @@ import React from 'react'; +import update from 'react-addons-update'; /*abstract*/ class Paginated extends React.Component{ constructor(){ super(); } - setupPagination(itemsPerPage, totalItems){ + updatePagination(itemsPerPage, totalItems){ let currentPage = 1; + const totalPages = this.totalPages(itemsPerPage, totalItems); + if (this.state.pagination && this.state.pagination.currentPage !== undefined){ currentPage = this.state.pagination.currentPage; } + if (currentPage > totalPages) currentPage = totalPages; + this.setState({pagination: { - itemsPerPage: itemsPerPage, - totalItems: totalItems, - currentPage: currentPage + switchingPages: false, + itemsPerPage: itemsPerPage, + totalItems: totalItems, + currentPage: currentPage } }); + + this.handlePageChange = this.handlePageChange.bind(this); } - getPaginatedUrl(base, page){ + totalPages(itemsPerPage, totalItems){ + return Math.ceil(totalItems / itemsPerPage); + } + + getPaginatedUrl(base){ + const page = this.state.pagination && this.state.pagination.currentPage !== undefined + ? this.state.pagination.currentPage + : 1; + return base.replace(/#\{PAGE\}/g, page); } + setPaginationState(props, done){ + this.setState(update(this.state, { + pagination: { + $merge: props + } + }), done); + } + + handlePageChange(pageNum){ + return (e) => { + // Update current page, once rendering is completed, raise + // on page changed event + this.setPaginationState({ + currentPage: pageNum, + switchingPages: true + }, () => { + if (this.onPageChanged) this.onPageChanged(pageNum); + }); + } + } + + handlePageItemsNumChange(delta, needsRefreshCallback){ + const pagesBefore = this.totalPages(this.state.pagination.itemsPerPage, this.state.pagination.totalItems), + pagesAfter = this.totalPages(this.state.pagination.itemsPerPage, this.state.pagination.totalItems + delta); + let currentPage = this.state.pagination.currentPage; + + if (currentPage > pagesAfter) currentPage = pagesAfter; + + this.setPaginationState({ + totalItems: this.state.pagination.totalItems + delta, + currentPage: currentPage + }, () => { + if (pagesBefore !== pagesAfter) needsRefreshCallback(pagesAfter); + }); + } + render(){ throw new Error("Override me"); } diff --git a/app/static/app/js/components/Paginator.jsx b/app/static/app/js/components/Paginator.jsx index a878c30f..880e93d3 100644 --- a/app/static/app/js/components/Paginator.jsx +++ b/app/static/app/js/components/Paginator.jsx @@ -14,7 +14,7 @@ class Paginator extends React.Component {
  • - + «
  • @@ -22,10 +22,10 @@ class Paginator extends React.Component { return (
  • {page + 1}
  • ); + >{page + 1}); })}
  • - + »
  • diff --git a/app/static/app/js/components/ProjectList.jsx b/app/static/app/js/components/ProjectList.jsx index 99c1cdb3..fd089c96 100644 --- a/app/static/app/js/components/ProjectList.jsx +++ b/app/static/app/js/components/ProjectList.jsx @@ -1,9 +1,11 @@ import React from 'react'; import $ from 'jquery'; +import '../css/ProjectList.scss'; import ProjectListItem from './ProjectListItem'; import Paginated from './Paginated'; import Paginator from './Paginator'; +import ErrorMessage from './ErrorMessage'; class ProjectList extends Paginated { constructor(){ @@ -11,10 +13,13 @@ class ProjectList extends Paginated { this.state = { loading: true, + refreshing: false, error: "", - projects: null + projects: [] } + this.PROJECTS_PER_PAGE = 10; + this.handleDelete = this.handleDelete.bind(this); } @@ -23,15 +28,17 @@ class ProjectList extends Paginated { } refresh(){ + this.setState({refreshing: true}); + // Load projects from API this.serverRequest = - $.getJSON(this.props.source, json => { + $.getJSON(this.getPaginatedUrl(this.props.source), json => { if (json.results){ this.setState({ projects: json.results, loading: false }); - this.setupPagination(10, json.count); + this.updatePagination(this.PROJECTS_PER_PAGE, json.count); }else{ this.setState({ error: `Invalid JSON response: ${JSON.stringify(json)}`, @@ -44,8 +51,14 @@ class ProjectList extends Paginated { error: `Could not load projects list: ${textStatus}`, loading: false }); + }) + .always(() => { + this.setState({refreshing: false}); }); + } + onPageChanged(pageNum){ + this.refresh(); } componentWillUnmount(){ @@ -55,25 +68,25 @@ class ProjectList extends Paginated { handleDelete(projectId){ let projects = this.state.projects.filter(p => p.id !== projectId); this.setState({projects: projects}); + this.handlePageItemsNumChange(-1, () => { + this.refresh(); + }); } render() { if (this.state.loading){ - return (
    Loading projects...
    ); - } - else if (this.state.projects){ - return ( - -
      - {this.state.projects.map(p => ( - - ))} -
    -
    ); - }else if (this.state.error){ - return (
    An error occurred: {this.state.error}
    ); + return (
    Loading projects...
    ); }else{ - return (
    ); // should never happen + return (
    + + +
      + {this.state.projects.map(p => ( + + ))} +
    +
    +
    ); } } } diff --git a/app/static/app/js/css/ProjectList.scss b/app/static/app/js/css/ProjectList.scss new file mode 100644 index 00000000..bd857def --- /dev/null +++ b/app/static/app/js/css/ProjectList.scss @@ -0,0 +1,6 @@ +.project-list{ + ul.project-list.refreshing{ + opacity: 0.5; + pointer-events: none; + } +}