├── .DS_Store
├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── build
├── 89889688147bd7575d6327160d64e760.svg
├── index.js
└── static
│ └── media
│ ├── glyphicons-halflings-regular.448c34a5.woff2
│ ├── glyphicons-halflings-regular.89889688.svg
│ ├── glyphicons-halflings-regular.e18bbf61.ttf
│ ├── glyphicons-halflings-regular.f4769f9b.eot
│ └── glyphicons-halflings-regular.fa277232.woff
├── package-lock.json
├── package.json
├── public
└── index.html
├── src
├── .DS_Store
├── actions
│ └── dataTable.js
├── components
│ ├── DataTable.js
│ ├── DataTableLoading.js
│ └── DataTableRedux.js
├── index.js
└── reducers
│ └── dataTableReducer.js
├── webpack.config.js
└── yarn.lock
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenforthewin/react-redux-datatable/c3c7fb78d892ddc28935bd5bd7e25bf0a02f1dd1/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"],
3 | "plugins": [
4 | "transform-object-rest-spread",
5 | "transform-react-jsx"
6 | ]
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Kenneth Bergquist
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # redux-remote-datatable
2 |
3 | [Test it out here.](https://datatable.kenforthewin.com/)
4 | 
5 |
6 | ## Installation
7 |
8 | `npm i redux-remote-datatable --save`
9 |
10 | ## Usage
11 |
12 | - Add the reducer.
13 |
14 | ```javascript
15 | import { dataTableReducer } from 'redux-remote-datatable';
16 |
17 | ...
18 |
19 | const appReducer = persistCombineReducers(config, {
20 | dataTableReducer,
21 | ...
22 | });
23 | ```
24 |
25 | - Add the DataTableRedux component with its required initialization props.
26 |
27 | ```javascript
28 | import { DataTableRedux as DataTable } from 'redux-remote-datatable';
29 |
30 | ...
31 |
32 |
36 | ```
37 |
38 | ### Initialization props
39 |
40 | - `fields`: An object whose keys are the table header titles and whose values correspond with values in the received data objects.
41 | - `ajax`: The data url.
42 | - `idField`: The unique identifier field of the received data objects. Used when assigning keys to child elements.
43 |
44 | The component will make POST requests to the `ajax` endpoint for data to populate the table. In the body of the POST request will be a JSON object with the following parameters:
45 |
46 | ### Request parameters
47 |
48 | - `draw`: starts at 1 and is incremented by 1 every time data is requested from the remote server and the table is re-drawn.
49 | - `page`: The current page.
50 | - `perPage`: The amount of data objects to be requested and displayed per page.
51 | - `sortField`: The field to sort the data by. Can be null.
52 | - `sortDirection`: One of `asc` and `desc`.
53 | - `searchValue`: The user-inputted search string. Default is a blank string.
54 |
55 | ### Response parameters
56 |
57 | - `draw`: echo the draw from the request.
58 | - `totalRecords`: Record count before pagination.
59 | - `data`: An array of JSON objects with keys corresponding to the pre-defined fields.
60 |
61 | ### Example response
62 |
63 | ```json
64 | {
65 | "draw": "1",
66 | "totalRecords": 473,
67 | "data": [
68 | {
69 | "id": 52,
70 | "official_name": "Roy Blunt",
71 | "thomas_id": "01464",
72 | "birthday": "1950-01-10"
73 | },
74 | {
75 | "id": 51,
76 | "official_name": "Richard Blumenthal",
77 | "thomas_id": "02076",
78 | "birthday": "1946-02-13"
79 | },
80 | ...
81 | ]
82 | }
83 | ```
84 |
85 | ## Example backend
86 |
87 | - An example backend, written in Ruby on Rails, is available [here](https://github.com/kenforthewin/legislators-api).
88 | - A separate example backend written in Elixir (Phoenix) [here](https://github.com/kenforthewin/legislators-api-phoenix).
89 |
--------------------------------------------------------------------------------
/build/89889688147bd7575d6327160d64e760.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/build/static/media/glyphicons-halflings-regular.448c34a5.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenforthewin/react-redux-datatable/c3c7fb78d892ddc28935bd5bd7e25bf0a02f1dd1/build/static/media/glyphicons-halflings-regular.448c34a5.woff2
--------------------------------------------------------------------------------
/build/static/media/glyphicons-halflings-regular.e18bbf61.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenforthewin/react-redux-datatable/c3c7fb78d892ddc28935bd5bd7e25bf0a02f1dd1/build/static/media/glyphicons-halflings-regular.e18bbf61.ttf
--------------------------------------------------------------------------------
/build/static/media/glyphicons-halflings-regular.f4769f9b.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenforthewin/react-redux-datatable/c3c7fb78d892ddc28935bd5bd7e25bf0a02f1dd1/build/static/media/glyphicons-halflings-regular.f4769f9b.eot
--------------------------------------------------------------------------------
/build/static/media/glyphicons-halflings-regular.fa277232.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenforthewin/react-redux-datatable/c3c7fb78d892ddc28935bd5bd7e25bf0a02f1dd1/build/static/media/glyphicons-halflings-regular.fa277232.woff
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-remote-datatable",
3 | "version": "0.1.15",
4 | "description": "A React and Redux-based table for server-processed data.",
5 | "main": "build/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack",
9 | "start": "webpack-serve"
10 | },
11 | "author": "Kenny Bergquist",
12 | "license": "MIT",
13 | "dependencies": {
14 | "babel-polyfill": "^6.26.0",
15 | "bootstrap": "^3.3.7",
16 | "core-js": "^2.5.3",
17 | "es6-promise": "^4.2.4",
18 | "es6-promise-promise": "^1.0.0",
19 | "react": "^16.2.0",
20 | "react-bootstrap": "^0.32.1",
21 | "react-redux": "^5.0.7",
22 | "redux": "^3.7.2",
23 | "redux-thunk": "^2.2.0",
24 | "webpack": "^4.1.1"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/kenforthewin/react-redux-datatable"
29 | },
30 | "devDependencies": {
31 | "babel-cli": "^6.24.1",
32 | "babel-core": "^6.24.1",
33 | "babel-loader": "^7.0.0",
34 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
35 | "babel-plugin-transform-react-jsx": "^6.24.1",
36 | "babel-preset-env": "^1.5.1",
37 | "css-loader": "0.28.7",
38 | "file-loader": "^1.1.11",
39 | "html-webpack-plugin": "^3.0.6",
40 | "style-loader": "0.19.0",
41 | "url-loader": "^1.0.1",
42 | "webpack-cli": "^2.0.12",
43 | "webpack-dev-server": "^3.1.1",
44 | "webpack-serve": "^0.2.0"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | redux-remote-datatable
7 |
8 |
9 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kenforthewin/react-redux-datatable/c3c7fb78d892ddc28935bd5bd7e25bf0a02f1dd1/src/.DS_Store
--------------------------------------------------------------------------------
/src/actions/dataTable.js:
--------------------------------------------------------------------------------
1 | export const initializeDataTable = (ajax, fields, idField) => {
2 | return (dispatch, getState) => {
3 | const state = getState();
4 | const {
5 | page,
6 | perPage,
7 | sortDirection,
8 | sortField
9 | } = state.dataTableReducer;
10 | return requestData(ajax, 0, page, perPage, sortDirection, sortField, "")
11 | .then(responseJson => {
12 | dispatch({
13 | type: 'initialize_table',
14 | data: responseJson.data,
15 | ajax: ajax,
16 | totalRecords: responseJson.totalRecords,
17 | fields: fields,
18 | idField: idField,
19 | perPage: perPage
20 | });
21 | })
22 | }
23 | };
24 |
25 | export const sort = (field, direction = 'asc') => {
26 | return (dispatch, getState) => {
27 | const state = getState();
28 | const {
29 | ajax,
30 | draw,
31 | perPage,
32 | page,
33 | fields,
34 | searchValue
35 | } = state.dataTableReducer;
36 | if (Object.values(fields).includes(field) || field === null) {
37 | dispatch({ type: 'table_loading' });
38 | return requestData(ajax, draw, page, perPage, direction, field, searchValue)
39 | .then(responseJson => {
40 | dispatch({
41 | type: 'sort_table',
42 | data: responseJson.data,
43 | sortField: field,
44 | sortDirection: direction
45 | });
46 | });
47 | }
48 | }
49 | }
50 |
51 | export const searchTable = (searchValue) => {
52 | return (dispatch, getState) => {
53 | if (searchValue.length < 3) searchValue = '';
54 | dispatch({ type: 'table_loading' });
55 | const state = getState();
56 | const {
57 | ajax,
58 | draw,
59 | perPage,
60 | sortDirection,
61 | sortField
62 | } = state.dataTableReducer;
63 |
64 | return requestData(ajax, draw, 1, perPage, sortDirection, sortField, searchValue)
65 | .then(responseJson => {
66 | const newPage = 1;
67 | dispatch({
68 | type: 'search_table',
69 | data: responseJson.data,
70 | totalRecords: responseJson.totalRecords,
71 | page: newPage,
72 | searchValue
73 | })
74 | });
75 | }
76 | }
77 |
78 | export const goToPage = (page) => {
79 | return (dispatch, getState) => {
80 | const state = getState();
81 | const {
82 | ajax,
83 | draw,
84 | perPage,
85 | totalRecords,
86 | sortDirection,
87 | sortField,
88 | searchValue
89 | } = state.dataTableReducer;
90 | page = Number(page);
91 | if (page > 0 && page <= Math.ceil(totalRecords / perPage)) {
92 | dispatch({ type: 'table_loading' });
93 | return requestData(ajax, draw, page, perPage, sortDirection, sortField, searchValue)
94 | .then(responseJson => {
95 | dispatch({
96 | type: 'go_to_page',
97 | data: responseJson.data,
98 | page
99 | })
100 | });
101 | }
102 | };
103 | }
104 |
105 | export const changePerPage = (perPage) => {
106 | return (dispatch, getState) => {
107 | perPage = Number(perPage);
108 | const state = getState();
109 | const {
110 | ajax,
111 | draw,
112 | sortDirection,
113 | sortField,
114 | searchValue
115 | } = state.dataTableReducer;
116 | if ([10, 25, 50, 100].includes(perPage)) {
117 | dispatch({ type: 'table_loading' });
118 | return requestData(ajax, draw, 1, perPage, sortDirection, sortField, searchValue)
119 | .then(responseJson => {
120 | dispatch({
121 | type: 'change_per_page',
122 | data: responseJson.data,
123 | perPage
124 | })
125 | });
126 | }
127 | }
128 | }
129 |
130 | export const ellipRight = () => {
131 | return (dispatch, getState) => {
132 | const state = getState();
133 | const {
134 | ajax,
135 | draw,
136 | page,
137 | perPage,
138 | totalRecords,
139 | sortDirection,
140 | sortField,
141 | searchValue
142 | } = state.dataTableReducer;
143 | const totalPages = Math.ceil(totalRecords / perPage);
144 | if (totalPages - 4 >= page) {
145 | dispatch({ type: 'table_loading' });
146 | return requestData(ajax, draw, page + 4, perPage, sortDirection, sortField, searchValue)
147 | .then(responseJson => {
148 | dispatch({
149 | type: 'go_to_page',
150 | data: responseJson.data,
151 | page: page + 4
152 | })
153 | })
154 | }
155 | }
156 | }
157 |
158 | export const ellipLeft = () => {
159 | return (dispatch, getState) => {
160 | const state = getState();
161 | const {
162 | ajax,
163 | draw,
164 | page,
165 | perPage,
166 | sortDirection,
167 | sortField,
168 | searchValue
169 | } = state.dataTableReducer;
170 | if (page >= 4) {
171 | dispatch({ type: 'table_loading' });
172 | return requestData(ajax, draw, page - 4, perPage, sortDirection, sortField, searchValue)
173 | .then(responseJson => {
174 | dispatch({
175 | type: 'go_to_page',
176 | data: responseJson.data,
177 | page: page - 4
178 | })
179 | })
180 | }
181 | }
182 | }
183 |
184 | export const nextPage = () => {
185 | return (dispatch, getState) => {
186 | const state = getState();
187 | const {
188 | ajax,
189 | draw,
190 | page,
191 | perPage,
192 | totalRecords,
193 | sortDirection,
194 | sortField,
195 | searchValue
196 | } = state.dataTableReducer;
197 |
198 | if (page * perPage + 1 <= totalRecords) {
199 | dispatch({ type: 'table_loading' });
200 |
201 | return requestData(ajax, draw, page + 1, perPage, sortDirection, sortField, searchValue)
202 | .then(responseJson => {
203 | dispatch({
204 | type: 'next_page',
205 | data: responseJson.data
206 | })
207 | });
208 | }
209 | }
210 | };
211 |
212 | export const previousPage = () => {
213 | return (dispatch, getState) => {
214 | const state = getState();
215 | const {
216 | ajax,
217 | draw,
218 | page,
219 | perPage,
220 | sortDirection,
221 | sortField,
222 | searchValue
223 | } = state.dataTableReducer;
224 |
225 | if (page > 1) {
226 | dispatch({ type: 'table_loading' });
227 |
228 | return requestData(ajax, draw, page - 1, perPage, sortDirection, sortField, searchValue)
229 | .then(responseJson => {
230 | dispatch({
231 | type: 'previous_page',
232 | data: responseJson.data
233 | })
234 | });
235 | }
236 | }
237 | };
238 |
239 | const requestBody = (draw, page, perPage, sortDirection, sortField, searchValue) => {
240 | return JSON.stringify({
241 | draw,
242 | page,
243 | perPage,
244 | sortDirection,
245 | sortField,
246 | searchValue
247 | });
248 | };
249 |
250 | const requestData = (ajax, draw, page, perPage, sortDirection, sortField, searchValue) => {
251 | return fetch(ajax, {
252 | method: 'POST',
253 | mode: 'cors',
254 | redirect: 'follow',
255 | referrer: 'no-referrer',
256 | headers: {
257 | 'content-type': 'application/json'
258 | },
259 | body: requestBody(draw, page, perPage, sortDirection, sortField, searchValue)
260 | })
261 | .then(response => response.json());
262 | };
--------------------------------------------------------------------------------
/src/components/DataTable.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Table, Pagination, Col, Glyphicon, FormGroup, FormControl } from 'react-bootstrap';
4 | import 'bootstrap/dist/css/bootstrap.css';
5 | import PropTypes from 'prop-types';
6 | import Loading from './DataTableLoading';
7 | const WAIT_INTERVAL = 1000;
8 | const ENTER_KEY = 13;
9 | const pageInputStyle = {
10 | marginLeft: '5px',
11 | marginRight: '5px'
12 | }
13 | const sortLinkStyle = {
14 | marginLeft: '10px',
15 | color: 'darkgray'
16 | }
17 |
18 | class DataTable extends Component {
19 | componentWillMount() {
20 | this.timer = null;
21 | this.props.initializeDataTable(this.props.ajax, this.props.fields, this.props.idField);
22 | }
23 |
24 | columnSize() {
25 | const tableSize = Object.keys(this.props.fields).length;
26 | let colSize = Math.floor(12 / tableSize);
27 | if (colSize === 0)
28 | colSize = 1;
29 | return 'col-md-' + colSize;
30 | }
31 |
32 | renderHead() {
33 | const fields = this.props.fields;
34 | const keys = Object.keys(fields);
35 | const th = keys.map((key) => {
36 | return({key}{this.renderSort(fields[key])} | );
37 | })
38 | return (
39 |
40 |
41 | {th}
42 |
43 |
44 | );
45 | }
46 |
47 | renderSort(key) {
48 | const ascActive = this.props.sortField === key && this.props.sortDirection === 'asc';
49 | const descActive = this.props.sortField === key && this.props.sortDirection === 'desc';
50 | const ascColor = ascActive ? 'black' : 'darkgray';
51 | const descColor = descActive ? 'black' : 'darkgray';
52 | return (
53 | this.onSortClick(key, ascActive, descActive)}>
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | onSortClick(field, ascActive, descActive) {
61 | if (ascActive) {
62 | this.props.sort(field, 'desc');
63 | } else if (descActive) {
64 | this.props.sort(null);
65 | } else {
66 | this.props.sort(field, 'asc')
67 | }
68 | }
69 |
70 | getTotalPages() {
71 | return Math.ceil(this.props.totalRecords / this.props.perPage);
72 | }
73 |
74 | renderPageButtons() {
75 | const totalPages = this.getTotalPages();
76 | const page = this.props.page;
77 | const previousActive = page > 1;
78 | const nextActive = page < totalPages;
79 | const previousLink = this.props.previousPage()}>Previous;
80 | const nextLink = this.props.nextPage()}>Next;
81 |
82 | let links = [];
83 | if(totalPages <= 10) {
84 | for (let i = 1; i <= totalPages; i++) {
85 | const active = page === i;
86 | links.push({i});
87 | }
88 | } else {
89 | if (page > 4)
90 | links.push( this.props.ellipLeft()} />);
91 | for (let i = page - 8; i <= page + 8; i++) {
92 | const active = page === i;
93 | if (i > 0 && i <= totalPages) {
94 | if (i < page - 3) {
95 | if (page + 4 <= totalPages && page - 4 >= 1) continue;
96 |
97 | const leftPad = 7 - (totalPages - page);
98 | if ((page - i) <= leftPad) {
99 | links.push( this.props.goToPage(i)} active={active}>{i});
100 | }
101 | } else if (i > page + 3) {
102 | if (totalPages >= page + 4 && page - 4 >= 1) continue;
103 |
104 | const rightPad = 8 - page;
105 | if ((i - page) <= rightPad) {
106 | links.push( this.props.goToPage(i)} active={active}>{i});
107 | }
108 | } else {
109 | links.push( this.props.goToPage(i)} active={active}>{i});
110 | }
111 | }
112 | }
113 | if (page <= totalPages - 4)
114 | links.push( this.props.ellipRight()} />);
115 | }
116 |
117 |
118 | return (
119 |
120 |
121 | {previousLink}
122 | {links}
123 | {nextLink}
124 |
125 |
126 | );
127 | }
128 |
129 | renderBody() {
130 | const values = Object.values(this.props.fields);
131 | const data = this.props.data;
132 |
133 | const tr = data.map((datum) => {
134 | const td = values.map((field) => {
135 | var tdId = String(datum[this.props.idField]) + "-" + field;
136 | return({datum[field]} | );
137 | });
138 |
139 | return(
140 |
141 | {td}
142 |
143 | );
144 | });
145 | return (
146 |
147 | {tr}
148 |
149 | );
150 | }
151 |
152 | handlePageInput(event) {
153 | clearTimeout(this.timer);
154 | const newPage = Number(event.target.value);
155 | if (newPage < 1 || newPage > this.getTotalPages()) {
156 | event.target.value = '';
157 | return;
158 | }
159 | event.persist();
160 | this.timer = setTimeout(() => {
161 | if (event.target !== null)
162 | this.props.goToPage(event.target.value);
163 | event.target.placeholder = event.target.value;
164 | event.target.value = '';
165 | }, WAIT_INTERVAL);
166 | }
167 |
168 |
169 |
170 | handlePageInputKeyDown(event) {
171 | const value = event.target.value;
172 | const newPage = Number(event.target.value);
173 |
174 | if (newPage < 1 || newPage > this.getTotalPages()) {
175 | event.target.value = '';
176 | return;
177 | }
178 | if (event.keyCode === ENTER_KEY) {
179 | clearTimeout(this.timer);
180 | this.props.goToPage(value);
181 | event.target.placeholder = value;
182 | event.target.value = '';
183 | }
184 | }
185 |
186 | handleSearchChange(event) {
187 | const value = event.target.value;
188 | clearTimeout(this.timer);
189 | this.timer = setTimeout(() => {
190 | if (value !== null)
191 | this.props.searchTable(value);
192 |
193 | }, WAIT_INTERVAL);
194 | }
195 |
196 | renderLoading() {
197 | if (!this.props.loading) return;
198 | return (
199 |
200 | );
201 | }
202 |
203 | renderSearch() {
204 | return (
205 |
206 |
211 |
212 | )
213 | }
214 |
215 | render() {
216 | return (
217 |
218 |
219 | {this.renderSearch()}
220 |
221 |
222 | {this.renderHead()}
223 | {this.renderBody()}
224 |
225 | {this.renderLoading()}
226 |
227 |
Page
228 |
229 | this.handlePageInput(event)} onKeyDown={(event) => this.handlePageInputKeyDown(event)} placeholder={this.props.page} />
230 | of {this.getTotalPages()}
231 |
232 |
233 | {this.renderPageButtons()}
234 |
235 |
236 |
237 |
238 |
244 | per page
245 |
246 |
247 |
248 |
249 | );
250 | }
251 | }
252 |
253 | DataTable.propTypes = {
254 | fields: PropTypes.object,
255 | data: PropTypes.array,
256 | idField: PropTypes.string,
257 | totalRecords: PropTypes.number,
258 | perPage: PropTypes.number,
259 | page: PropTypes.number
260 | }
261 |
262 | export default DataTable;
--------------------------------------------------------------------------------
/src/components/DataTableLoading.js:
--------------------------------------------------------------------------------
1 | // inspired by turbolinks: github.com/turbolinks/turbolinks
2 | import React, { Component } from 'react';
3 | class DataTableLoading extends Component {
4 | constructor(props) {
5 | super()
6 | this.state = {
7 | styles: {
8 | width: 0,
9 | position: 'fixed',
10 | display: 'block',
11 | height: '2px',
12 | background: '#0076ff',
13 | top: 0,
14 | left: 0,
15 | transform: "translate3d(0, 0, 0)",
16 | zIndex: 9999,
17 | transition: 'width 300ms ease-out, opacity 150ms 150ms ease-in',
18 | }
19 | }
20 | }
21 |
22 | componentWillMount() {
23 | const value = 1;
24 |
25 | this.setState({
26 | ...this.state,
27 | styles: {
28 | ...this.state.styles,
29 | width: (10 + value * 90)
30 | }
31 | });
32 | this.loadingTimeout = this.loadingTimeout || setInterval(this.increment.bind(this), 50
33 | );
34 | }
35 |
36 | increment() {
37 | const value = 45;
38 | this.setState({
39 | ...this.state,
40 | styles: {
41 | ...this.state.styles,
42 | width: this.state.styles.width + (value + (Math.random() / 100))
43 | }
44 | });
45 | }
46 |
47 | componentWillUnmount() {
48 | clearInterval(this.loadingTimeout);
49 | }
50 |
51 | render() {
52 | return (
53 |
54 | )
55 | }
56 | }
57 |
58 | export default DataTableLoading;
--------------------------------------------------------------------------------
/src/components/DataTableRedux.js:
--------------------------------------------------------------------------------
1 | import DataTable from './DataTable';
2 | import { connect } from 'react-redux';
3 | import {
4 | initializeDataTable,
5 | nextPage,
6 | previousPage,
7 | goToPage,
8 | ellipLeft,
9 | ellipRight,
10 | changePerPage,
11 | sort,
12 | searchTable
13 | } from '../actions/dataTable';
14 |
15 | const mapStateToProps = ({ dataTableReducer }) => {
16 | const { draw, page, perPage, totalRecords, data, loading, sortDirection, sortField, searchValue } = dataTableReducer;
17 |
18 | return {
19 | page,
20 | perPage,
21 | draw,
22 | totalRecords,
23 | data,
24 | loading,
25 | sortDirection,
26 | sortField,
27 | searchValue
28 | };
29 | };
30 |
31 | const mapDispatchToProps = {
32 | initializeDataTable,
33 | nextPage,
34 | previousPage,
35 | goToPage,
36 | ellipLeft,
37 | ellipRight,
38 | changePerPage,
39 | sort,
40 | searchTable
41 | };
42 |
43 | export default connect(mapStateToProps, mapDispatchToProps)(DataTable);
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import DataTable from './components/DataTable';
2 | import DataTableRedux from './components/DataTableRedux';
3 | import dataTableReducer from './reducers/dataTableReducer';
4 | import ReactDOM from 'react-dom';
5 |
6 | export {
7 | DataTable,
8 | DataTableRedux,
9 | dataTableReducer
10 | };
11 |
12 | module.exports = {
13 | DataTable,
14 | DataTableRedux,
15 | dataTableReducer
16 | };
17 |
18 | // ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/src/reducers/dataTableReducer.js:
--------------------------------------------------------------------------------
1 | const INITIAL_STATE = {
2 | draw: 0,
3 | page: 1,
4 | perPage: 10,
5 | data: [],
6 | loading: true,
7 | ajax: '',
8 | totalRecords: 0,
9 | fields: {},
10 | idField: '',
11 | sortField: null,
12 | sortDirection: null,
13 | searchValue: ''
14 | }
15 |
16 | export default (state = INITIAL_STATE, action) => {
17 | switch (action.type) {
18 | case 'table_loading':
19 | return {
20 | ...state,
21 | loading: true,
22 | draw: state.draw + 1
23 | }
24 | case 'search_table':
25 | return {
26 | ...state,
27 | loading: false,
28 | data: action.data,
29 | totalRecords: action.totalRecords,
30 | page: action.page,
31 | searchValue: action.searchValue
32 | }
33 | case 'go_to_page':
34 | return {
35 | ...state,
36 | loading: false,
37 | page: action.page,
38 | data: action.data,
39 | }
40 | case 'change_per_page':
41 | return {
42 | ...state,
43 | loading: false,
44 | perPage: action.perPage,
45 | data: action.data,
46 | page: 1
47 | }
48 | case 'sort_table':
49 | return {
50 | ...state,
51 | loading: false,
52 | data: action.data,
53 | sortField: action.sortField,
54 | sortDirection: action.sortDirection
55 | }
56 | case 'initialize_table':
57 | return {
58 | ...state,
59 | data: action.data,
60 | loading: false,
61 | draw: 1,
62 | ajax: action.ajax,
63 | totalRecords: action.totalRecords,
64 | fields: action.fields,
65 | idField: action.idField,
66 | searchValue: ''
67 | }
68 | case 'next_page':
69 | return {
70 | ...state,
71 | data: action.data,
72 | page: state.page + 1,
73 | loading: false
74 | };
75 | case 'previous_page':
76 | return {
77 | ...state,
78 | data: action.data,
79 | page: state.page - 1,
80 | loading: false
81 | };
82 | default:
83 | return state;
84 | }
85 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | module.exports = {
3 | entry: './src/index.js',
4 | output: {
5 | path: path.resolve(__dirname, 'build'),
6 | filename: 'index.js',
7 | libraryTarget: 'commonjs2'
8 | },
9 | module: {
10 | rules: [ {
11 | oneOf: [
12 | {
13 | test: /\.js$/,
14 | include: path.resolve(__dirname, 'src'),
15 | exclude: /(node_modules|bower_components|build)/,
16 | use: {
17 | loader: 'babel-loader',
18 | options: {
19 | presets: ['env']
20 | }
21 | }
22 | },
23 | {
24 | test: /\.css$/,
25 | use: [
26 | require.resolve('style-loader'),
27 | {
28 | loader: require.resolve('css-loader'),
29 | options: {
30 | importLoaders: 1,
31 | },
32 | }
33 | ]
34 | },
35 | { test: /\.(woff|ttf|eot|svg)(\?v=[a-z0-9]\.[a-z0-9]\.[a-z0-9])?$/, loader: 'url-loader?limit=100000' },
36 | {
37 | // Exclude `js` files to keep "css" loader working as it injects
38 | // its runtime that would otherwise processed through "file" loader.
39 | // Also exclude `html` and `json` extensions so they get processed
40 | // by webpacks internal loaders.
41 | exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
42 | loader: require.resolve('file-loader'),
43 | options: {
44 | name: 'static/media/[name].[hash:8].[ext]',
45 | },
46 | }
47 | ]
48 | }
49 | ]
50 | },
51 | externals: {
52 | 'react': 'commonjs react' // this line is just to use the React dependency of our parent-testing-project instead of using our own React.
53 | },
54 | performance: {
55 | hints: false,
56 | },
57 | };
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | asap@~2.0.3:
6 | version "2.0.6"
7 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
8 |
9 | bootstrap@^3.3.7:
10 | version "3.3.7"
11 | resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71"
12 |
13 | core-js@^1.0.0:
14 | version "1.2.7"
15 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
16 |
17 | encoding@^0.1.11:
18 | version "0.1.12"
19 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
20 | dependencies:
21 | iconv-lite "~0.4.13"
22 |
23 | fbjs@^0.8.16:
24 | version "0.8.16"
25 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
26 | dependencies:
27 | core-js "^1.0.0"
28 | isomorphic-fetch "^2.1.1"
29 | loose-envify "^1.0.0"
30 | object-assign "^4.1.0"
31 | promise "^7.1.1"
32 | setimmediate "^1.0.5"
33 | ua-parser-js "^0.7.9"
34 |
35 | hoist-non-react-statics@^2.5.0:
36 | version "2.5.0"
37 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz#d2ca2dfc19c5a91c5a6615ce8e564ef0347e2a40"
38 |
39 | iconv-lite@~0.4.13:
40 | version "0.4.19"
41 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
42 |
43 | invariant@^2.0.0:
44 | version "2.2.3"
45 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.3.tgz#1a827dfde7dcbd7c323f0ca826be8fa7c5e9d688"
46 | dependencies:
47 | loose-envify "^1.0.0"
48 |
49 | is-stream@^1.0.1:
50 | version "1.1.0"
51 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
52 |
53 | isomorphic-fetch@^2.1.1:
54 | version "2.2.1"
55 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
56 | dependencies:
57 | node-fetch "^1.0.1"
58 | whatwg-fetch ">=0.10.0"
59 |
60 | js-tokens@^3.0.0:
61 | version "3.0.2"
62 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
63 |
64 | lodash-es@^4.17.5, lodash-es@^4.2.1:
65 | version "4.17.7"
66 | resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.7.tgz#db240a3252c3dd8360201ac9feef91ac977ea856"
67 |
68 | lodash@^4.17.5, lodash@^4.2.1:
69 | version "4.17.5"
70 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
71 |
72 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
73 | version "1.3.1"
74 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
75 | dependencies:
76 | js-tokens "^3.0.0"
77 |
78 | node-fetch@^1.0.1:
79 | version "1.7.3"
80 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
81 | dependencies:
82 | encoding "^0.1.11"
83 | is-stream "^1.0.1"
84 |
85 | object-assign@^4.1.0, object-assign@^4.1.1:
86 | version "4.1.1"
87 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
88 |
89 | promise@^7.1.1:
90 | version "7.3.1"
91 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
92 | dependencies:
93 | asap "~2.0.3"
94 |
95 | prop-types@^15.6.0:
96 | version "15.6.1"
97 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
98 | dependencies:
99 | fbjs "^0.8.16"
100 | loose-envify "^1.3.1"
101 | object-assign "^4.1.1"
102 |
103 | react-redux@^5.0.7:
104 | version "5.0.7"
105 | resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.7.tgz#0dc1076d9afb4670f993ffaef44b8f8c1155a4c8"
106 | dependencies:
107 | hoist-non-react-statics "^2.5.0"
108 | invariant "^2.0.0"
109 | lodash "^4.17.5"
110 | lodash-es "^4.17.5"
111 | loose-envify "^1.1.0"
112 | prop-types "^15.6.0"
113 |
114 | react@^16.2.0:
115 | version "16.2.0"
116 | resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
117 | dependencies:
118 | fbjs "^0.8.16"
119 | loose-envify "^1.1.0"
120 | object-assign "^4.1.1"
121 | prop-types "^15.6.0"
122 |
123 | redux-thunk@^2.2.0:
124 | version "2.2.0"
125 | resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5"
126 |
127 | redux@^3.7.2:
128 | version "3.7.2"
129 | resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b"
130 | dependencies:
131 | lodash "^4.2.1"
132 | lodash-es "^4.2.1"
133 | loose-envify "^1.1.0"
134 | symbol-observable "^1.0.3"
135 |
136 | setimmediate@^1.0.5:
137 | version "1.0.5"
138 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
139 |
140 | symbol-observable@^1.0.3:
141 | version "1.2.0"
142 | resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
143 |
144 | ua-parser-js@^0.7.9:
145 | version "0.7.17"
146 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
147 |
148 | whatwg-fetch@>=0.10.0:
149 | version "2.0.3"
150 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
151 |
--------------------------------------------------------------------------------