├── .babelrc ├── .gitignore ├── .idea ├── .gitignore ├── misc.xml ├── modules.xml ├── react-strap-table.iml └── vcs.xml ├── .npmignore ├── README.md ├── examples └── src │ ├── advance.html │ ├── advance.js │ ├── index.html │ └── index.js ├── package-lock.json ├── package.json ├── src ├── index.js └── styles.css └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /workspace.xml -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/react-strap-table.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | examples 3 | .babelrc 4 | .gitignore 5 | webpack.config.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-strap-table 2 | react table (client and server side) based on bootstrap style. 3 | 4 | You can customize any thing (headings, contents ...) not only by text but you can putting your component or html tags, 5 | and will find css classes for all headings and contents of columns, 6 | and you can sorting, pagination, limit of per page and filtering of server-side data. 7 | 8 | ## Installation 9 | ``` 10 | npm install react-strap-table 11 | ``` 12 | ### components 13 | 14 | - ServerTable (server-side data) 15 | ``` 16 | import ServerTable from 'react-strap-table'; 17 | ``` 18 | - ClientTable (client-side data): 19 | > Coming soon... 20 | ## Demo (server-side) 21 | - **[Simple](https://asemalalami.github.io/react-strap-table/index)** (Demo without options ) 22 | - **[Advance](https://asemalalami.github.io/react-strap-table/advance)** 23 | ## Features 24 | - Customize headings of columns not only text but you can put your component or html tags. 25 | - Customize contents of columns (`td` cell) by any component and the row of data with passed for it. 26 | - Server-side sorting, pagination, limit of per page and filtering. 27 | - Customize response of your url request. 28 | - Every columns have css class for customize it `table-{colmun}-th` and `table-{colmun}-td`. 29 | - Customize texts and icons. 30 | 31 | ## Documentation: 32 | ### Example 33 | ```javascript 34 | const url = 'https://react-strap-table.com/users'; 35 | const columns = ['id', 'name', 'email', 'created_at']; 36 | const options = { 37 | headings: {id: '#', created_at: 'Created At'}, 38 | sortable: ['name', 'email'] 39 | }; 40 | return ( 41 | 45 | ); 46 | ``` 47 | ### Props 48 | - **url** (required, server-side): base url 49 | - **columns** (required, array): contains names of columns, the headings of columns will be Upper case of first letter but you can overwrite it by `headings` in [options](#options) props. 50 | - **hover** (boolean): bootstrap style, 51 | - **bordered** (boolean): bootstrap style, 52 | - **condensed** (boolean): bootstrap style, 53 | - **striped** (boolean): bootstrap style, 54 | - **options** (object): [details](#options). 55 | - **perPage** (boolean): show "PrePage" select input 56 | - **search** (boolean): show "search" input 57 | - **pagination** (boolean): show "pagination" section 58 | - **updateUrl** (boolean): if you want to update(replace) url page 59 | ### Children 60 | The children must be `Function` of two parameters `(row, column)`, and implementation of function must be `switch` of case columns that you need to customize contents. 61 | The `row` parameter with return current row of data. 62 | ```javascript 63 | 64 | { 65 | function (row, column) { 66 | switch (column) { 67 | case 'id': 68 | return ( 69 | 72 | ); 73 | case 'avatar': 74 | return (); 75 | default: 76 | return (row[column]); 77 | } 78 | } 79 | } 80 | 81 | ``` 82 | > Note: You can get the index of the current row using `row.index` 83 | > 84 | > Don't forget `default` case for other columns. 85 | ### Options 86 | Option|Description | Default 87 | ------|----------------------|------ 88 | headings|`object` of Headings columns `{id:'#', created_at: 'Created At'}`|`{}` 89 | sortable|`array` of columns that can sorted `['name', 'email']`|`[]` 90 | columnsWidth|`object` of width columns by percentage(%) and you can fix it by pixel(px)`{id: 5, name:30, created_at:'30px'}`|`{}` 91 | columnsAlign|`object` of align heading columns (not `td`) `{id: 'center'}`|`{}` 92 | perPage|`integer` limit rows of page|`10` 93 | perPageValues|`array` contain values of per page select|`[10, 20, 25, 100]` 94 | icons|`object` contains icons in table|`{sortBase: 'fa fa-sort',sortUp: 'fa fa-sort-amount-up',sortDown: 'fa fa-sort-amount-down',search: 'fa fa-search'}` 95 | texts|`object` contains texts in table|`{show: 'Show', entries: 'entries', showing: 'Showing', to: 'to', of: 'of', search: 'Search'}` 96 | requestParametersNames|`object` contains names of parameters request|`{query: 'query',limit: 'limit',page: 'page',orderBy: 'orderBy',direction: 'direction'}` 97 | orderDirectionValues|`object` contains names of order direction|`{ascending: 'asc',descending: 'desc'}` 98 | loading|`text\|html tag\|component` for loading|`(
Loading...
)` 99 | responseAdapter (server-side)|`function` if you want to mapping response. function take parameter of data response and must return `object` contains `data` and `total` properties.|`function (resp_data) {return {data: resp_data.data, total: resp_data.total}}` 100 | 101 | ### Options Examples 102 | ```javascript 103 | let checkAllInput = (); 105 | const options = { 106 | perPage: 5, 107 | headings: {id: checkAllInput, created_at: 'Created At'}, 108 | sortable: ['name', 'email', 'created_at'], 109 | columnsWidth: {name: 30, email: 30, id: 5}, 110 | columnsAlign: {id: 'center', avatar: 'center'}, 111 | requestParametersNames: {query: 'search', direction: 'order'}, 112 | responseAdapter: function (resp_data) { 113 | return {data: resp_data.data, total: resp_data.meta.total} 114 | }, 115 | texts: { 116 | show: 'عرض' 117 | }, 118 | icons: { 119 | sortUp: 'fa fa-sort-up', 120 | sortDown: 'fa fa-sort-down' 121 | } 122 | }; 123 | ``` 124 | 125 | ### Request parameters (server-side) 126 | Get Request with following parameters: 127 | - `query`: search input value. 128 | - `limit`: rows per page. 129 | - `page`: current page. 130 | - `orderBy`: column to sort. 131 | - `direction`: direction order? asc or desc. 132 | > You can rename the names by using `requestParametersNames` property in `options` prop 133 | > 134 | > You can change the direction order values by using `orderDirectionValues` property in `options` prop 135 | 136 | ### Css Classes 137 | The component based on `card` bootstrap component. 138 | 139 | - `react-strap-table`: root class of component. 140 | - `card-header`: contains per page and search input. 141 | - `card-body`: contains the table. 142 | - `card-footer`: contains pagination and data info. 143 | - `table-{column-name}-th`: table column header for every columns. 144 | - `table-{column-name}-td`: table column content for every columns. 145 | - `table-sort-icon`: span icon of table column header for columns that be sortable. 146 | 147 | ### Refresh Data 148 | You can refresh data in table by using `refreshData` function 149 | ```javascript 150 | class YouComponent extends Component { 151 | constructor(props) { 152 | ... 153 | this.serverTable = React.createRef(); 154 | } 155 | 156 | refreshTableData() { 157 | ... 158 | this.serverTable.current.refreshData(); 159 | ... 160 | } 161 | 162 | render() { 163 | return ( 164 | ... 165 | 166 | ... 167 | ); 168 | } 169 | } 170 | ``` 171 | -------------------------------------------------------------------------------- /examples/src/advance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Advance Demo 4 | 5 | 6 | 7 | 8 | 26 | 27 | 28 | 31 |
32 | 33 | -------------------------------------------------------------------------------- /examples/src/advance.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import _ from 'lodash'; 4 | import ServerTable from '../../src'; 5 | 6 | class App extends Component { 7 | 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | selectedUsers: [], 12 | usersIDs: [], 13 | isAllChecked: false, 14 | }; 15 | 16 | this.handleCheckboxTableChange = this.handleCheckboxTableChange.bind(this); 17 | this.handleCheckboxTableAllChange = this.handleCheckboxTableAllChange.bind(this); 18 | this.check_all = React.createRef(); 19 | } 20 | 21 | handleCheckboxTableChange(event) { 22 | const value = event.target.value; 23 | let selectedUsers = this.state.selectedUsers.slice(); 24 | 25 | selectedUsers.includes(value) ? 26 | selectedUsers.splice(selectedUsers.indexOf(value), 1) : 27 | selectedUsers.push(value); 28 | 29 | this.setState({selectedUsers: selectedUsers}, ()=>{ 30 | this.check_all.current.checked = _.difference(this.state.usersIDs, this.state.selectedUsers).length === 0; 31 | }); 32 | 33 | alert('Selected users ID: ' + selectedUsers.join(', ')); 34 | } 35 | 36 | handleCheckboxTableAllChange(event){ 37 | this.setState({selectedUsers: [...new Set(this.state.selectedUsers.concat(this.state.usersIDs))]}, ()=>{ 38 | this.check_all.current.checked = _.difference(this.state.usersIDs, this.state.selectedUsers).length === 0; 39 | }); 40 | } 41 | 42 | render() { 43 | let self = this; 44 | const url = 'https://5efe2a74dd373900160b3f24.mockapi.io/api/users'; 45 | const columns = ['id', 'name', 'email', 'avatar', 'address', 'created_at', 'actions']; 46 | let checkAllInput = (); 48 | const options = { 49 | perPage: 5, 50 | headings: {id: checkAllInput, created_at: 'Created At'}, 51 | sortable: ['name', 'email', 'created_at'], 52 | columnsWidth: {name: 30, email: 30, id: 5}, 53 | columnsAlign: {id: 'center', avatar: 'center', address: 'center'}, 54 | requestParametersNames: {query: 'search', direction: 'order'}, 55 | responseAdapter: function (resp_data) { 56 | let usersIDs = resp_data.data.map(a => a.id); 57 | self.setState({usersIDs: usersIDs}, () => { 58 | self.check_all.current.checked = _.difference(self.state.usersIDs, self.state.selectedUsers).length === 0; 59 | }); 60 | 61 | return {data: resp_data.data, total: resp_data.total} 62 | }, 63 | texts: { 64 | show: 'عرض' 65 | }, 66 | }; 67 | 68 | return ( 69 | 70 | { 71 | function (row, column) { 72 | switch (column) { 73 | case 'id': 74 | return ( 75 | 78 | ); 79 | case 'avatar': 80 | return (); 81 | case 'address': 82 | return ( 83 |
    84 |
  • Street: {row.address.address1}
  • 85 |
  • City: {row.address.city}
  • 86 |
  • Country: {row.address.country}
  • 87 |
88 | ); 89 | case 'actions': 90 | return ( 91 |
92 | 94 | 95 | 96 | 97 |
98 | ); 99 | default: 100 | return (row[column]); 101 | } 102 | } 103 | } 104 |
105 | ); 106 | } 107 | } 108 | 109 | render(, document.getElementById("root")); 110 | -------------------------------------------------------------------------------- /examples/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Simple Demo 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /examples/src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {render} from 'react-dom'; 3 | import ServerTable from '../../src'; 4 | 5 | class App extends Component { 6 | 7 | render() { 8 | const url = 'https://5efe2a74dd373900160b3f24.mockapi.io/api/users'; 9 | const columns = ['id', 'name', 'email', 'created_at']; 10 | const options = { 11 | headings: {id: '#', created_at: 'Created At'}, 12 | sortable: ['name', 'email'], 13 | requestParametersNames: {query: 'search', direction: 'order'}, 14 | }; 15 | 16 | return ( 17 | 18 | ); 19 | } 20 | } 21 | 22 | render(, document.getElementById("root")); 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-strap-table", 3 | "version": "1.0.7", 4 | "description": "react table (client and server side) based on bootstrap style", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "webpack-dev-server --mode development", 9 | "transpile": "babel src -d dist --copy-files", 10 | "prepublishOnly": "npm run transpile", 11 | "build": "webpack --mode production", 12 | "deploy": "gh-pages -d examples/dist", 13 | "publish-demo": "npm run build && npm run deploy", 14 | "publish": "npm publish" 15 | }, 16 | "keywords": [ 17 | "bootstrap", 18 | "table", 19 | "server-side", 20 | "client-side", 21 | "react", 22 | "pagination", 23 | "sort", 24 | "order", 25 | "search" 26 | ], 27 | "author": "Asem S. Alalami", 28 | "license": "ISC", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/AsemAlalami/react-strap-table.git" 32 | }, 33 | "peerDependencies": { 34 | "react": "^16.4.0", 35 | "react-dom": "^16.4.0" 36 | }, 37 | "devDependencies": { 38 | "@babel/cli": "^7.10.1", 39 | "@babel/core": "^7.10.2", 40 | "@babel/preset-env": "^7.10.2", 41 | "@babel/preset-react": "^7.10.1", 42 | "babel-loader": "^8.1.0", 43 | "css-loader": "^1.0.0", 44 | "gh-pages": "^3.0.0", 45 | "html-webpack-plugin": "^3.2.0", 46 | "react": "^16.4.2", 47 | "react-dom": "^16.4.2", 48 | "style-loader": "^0.21.0", 49 | "webpack": "^4.16.4", 50 | "webpack-cli": "^3.1.0", 51 | "webpack-dev-server": ">=3.1.11", 52 | "lodash": "latest" 53 | }, 54 | "dependencies": { 55 | "axios": ">=0.18.1", 56 | "prop-types": "^15.6.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import axios from 'axios'; 4 | import './styles.css'; 5 | 6 | class ServerTable extends Component { 7 | constructor(props) { 8 | super(props); 9 | 10 | if (this.props.columns === undefined || this.props.url === undefined) { 11 | throw "The prop 'columns' and 'url' is required."; 12 | } 13 | 14 | let default_texts = Object.assign(ServerTable.defaultProps.options.texts, {}); 15 | let default_icons = Object.assign(ServerTable.defaultProps.options.icons, {}); 16 | let default_parameters_names = Object.assign(ServerTable.defaultProps.options.requestParametersNames, {}); 17 | 18 | this.state = { 19 | options: Object.assign(ServerTable.defaultProps.options, this.props.options), 20 | requestData: { 21 | query: '', 22 | limit: 10, 23 | page: 1, 24 | orderBy: '', 25 | direction: 0, 26 | }, 27 | data: [], 28 | isLoading: true, 29 | }; 30 | this.state.requestData.limit = this.state.options.perPage; 31 | this.state.options.texts = Object.assign(default_texts, this.props.options.texts); 32 | this.state.options.icons = Object.assign(default_icons, this.props.options.icons); 33 | this.state.options.requestParametersNames = Object.assign(default_parameters_names, this.props.options.requestParametersNames); 34 | 35 | this.handlePerPageChange = this.handlePerPageChange.bind(this); 36 | this.table_search_input = React.createRef(); 37 | } 38 | 39 | shouldComponentUpdate(nextProps, nextState) { 40 | if (nextProps.url !== this.props.url) { 41 | this.setState({isLoading: true}, () => { 42 | this.handleFetchData(); 43 | }); 44 | } 45 | return true; 46 | } 47 | 48 | componentDidMount() { 49 | this.handleFetchData(); 50 | } 51 | 52 | tableClass() { 53 | let classes = 'table '; 54 | this.props.hover ? classes += 'table-hover ' : ''; 55 | this.props.bordered ? classes += 'table-bordered ' : ''; 56 | this.props.condensed ? classes += 'table-condensed ' : ''; 57 | this.props.striped ? classes += 'table-striped ' : ''; 58 | 59 | return classes; 60 | } 61 | 62 | renderColumns() { 63 | const columns = this.props.columns.slice(); 64 | const headings = this.state.options.headings; 65 | const options = this.state.options; 66 | const columns_width = this.state.options.columnsWidth; 67 | 68 | return columns.map((column) => ( 69 | this.handleSortColumnClick(column)}> 79 | {headings.hasOwnProperty(column) ? headings[column] : column.replace(/^\w/, c => c.toUpperCase())} 80 | { 81 | options.sortable.includes(column) && 83 | } 84 | 85 | )); 86 | } 87 | 88 | renderData() { 89 | const data = this.state.data.slice(); 90 | const columns = this.props.columns.slice(); 91 | const has_children = this.props.children !== undefined; 92 | let self = this; 93 | 94 | return data.map(function (row, row_index) { 95 | row.index = row_index; 96 | return ( 97 | 98 | { 99 | columns.map((column, index) => ( 100 | 101 | {has_children ? 102 | self.props.children(row, column) : 103 | row[column]} 104 | 105 | )) 106 | } 107 | 108 | ) 109 | }); 110 | } 111 | 112 | renderPagination() { 113 | const options = this.state.options; 114 | 115 | let pagination = []; 116 | 117 | pagination.push( 118 |
  • 120 | this.handlePageChange(1)}>« 121 |
  • 122 | ); 123 | for (let i = 1; i <= options.lastPage; i++) { 124 | pagination.push( 125 |
  • 126 | this.handlePageChange(i)}>{i} 127 |
  • 128 | ); 129 | } 130 | pagination.push( 131 |
  • 132 | this.handlePageChange(options.lastPage)}>» 133 |
  • 134 | ); 135 | 136 | return pagination; 137 | } 138 | 139 | handleSortColumnClick(column) { 140 | if (this.state.options.sortable.includes(column)) { 141 | const request_data = this.state.requestData; 142 | 143 | if (request_data.orderBy === column) { 144 | request_data.direction = request_data.direction === 1 ? 0 : 1; 145 | } else { 146 | request_data.orderBy = column; 147 | request_data.direction = 1; 148 | } 149 | 150 | this.setState({requestData: request_data, isLoading: true}, () => { 151 | this.handleFetchData(); 152 | }); 153 | } 154 | } 155 | 156 | refreshData() { 157 | this.setState({isLoading: true}, () => { 158 | this.handleFetchData(); 159 | }); 160 | } 161 | 162 | mapRequestData() { 163 | let parametersNames = this.state.options.requestParametersNames; 164 | let directionValues = Object.assign(this.props.options.orderDirectionValues || {}, ServerTable.defaultProps.options.orderDirectionValues); 165 | let requestData = this.state.requestData; 166 | 167 | return { 168 | [parametersNames.query]: requestData.query, 169 | [parametersNames.limit]: requestData.limit, 170 | [parametersNames.page]: requestData.page, 171 | [parametersNames.orderBy]: requestData.orderBy, 172 | [parametersNames.direction]: requestData.direction === 1 ? directionValues.ascending : directionValues.descending, 173 | }; 174 | } 175 | 176 | handleFetchData() { 177 | const url = this.props.url; 178 | let options = Object.assign({}, this.state.options); 179 | let requestData = Object.assign({}, this.state.requestData); 180 | let self = this; 181 | 182 | const urlParams = new URLSearchParams(this.mapRequestData()); 183 | let baseUrl = new URL(url); 184 | 185 | let com = baseUrl.search.length ? '&' : '?'; 186 | 187 | if (this.props.updateUrl) { 188 | history.replaceState(url, null, baseUrl.search + com + urlParams.toString()); 189 | } 190 | 191 | axios.get(url + com + urlParams.toString()) 192 | .then(function (response) { 193 | let response_data = response.data; 194 | 195 | let out_adapter = self.state.options.responseAdapter(response_data); 196 | if (out_adapter === undefined || !out_adapter || 197 | typeof out_adapter !== 'object' || out_adapter.constructor !== Object || 198 | !out_adapter.hasOwnProperty('data') || !out_adapter.hasOwnProperty('total')) { 199 | throw "You must return 'object' contains 'data' and 'total' attributes" 200 | } else if (out_adapter.data === undefined || out_adapter.total === undefined) { 201 | throw "Please check from returned data or your 'responseAdapter'. \n response must have 'data' and 'total' attributes."; 202 | } 203 | 204 | options.total = out_adapter.total; 205 | if (out_adapter.total === 0) { 206 | options.currentPage = 0; 207 | options.lastPage = 0; 208 | options.from = 0; 209 | options.to = 0; 210 | } else { 211 | options.currentPage = requestData.page; 212 | options.lastPage = Math.ceil(out_adapter.total / requestData.limit); 213 | options.from = requestData.limit * (requestData.page - 1) + 1; 214 | options.to = options.lastPage === options.currentPage ? options.total : requestData.limit * (requestData.page); 215 | } 216 | 217 | self.setState({data: out_adapter.data, options: options, isLoading: false}); 218 | }); 219 | } 220 | 221 | handlePerPageChange(event) { 222 | const {name, value} = event.target; 223 | let options = Object.assign({}, this.state.options); 224 | let requestData = Object.assign({}, this.state.requestData); 225 | 226 | options.perPage = value; 227 | requestData.limit = event.target.value; 228 | requestData.page = 1; 229 | 230 | this.setState({requestData: requestData, options: options, isLoading: true}, () => { 231 | this.handleFetchData(); 232 | }); 233 | } 234 | 235 | handlePageChange(page) { 236 | let requestData = Object.assign({}, this.state.requestData); 237 | requestData.page = page; 238 | 239 | this.setState({requestData: requestData, isLoading: true}, () => { 240 | this.handleFetchData(); 241 | }); 242 | } 243 | 244 | handleSearchClick() { 245 | let query = this.table_search_input.current.value; 246 | let requestData = Object.assign({}, this.state.requestData); 247 | requestData.query = query; 248 | requestData.page = 1; 249 | 250 | this.setState({requestData: requestData, isLoading: true}, () => { 251 | this.handleFetchData(); 252 | }); 253 | } 254 | 255 | render() { 256 | return ( 257 |
    258 | { 259 | (this.props.perPage || this.props.search) && 260 | 261 |
    262 | { 263 | this.props.perPage && 264 |
    265 | {this.state.options.texts.show} 266 | 274 | {this.state.options.texts.entries} 275 |
    276 | } 277 | 278 | {this.state.isLoading && (this.state.options.loading)} 279 | 280 | { 281 | this.props.search && 282 |
    283 | this.handleSearchClick()}/> 286 | 287 | 288 |
    289 | } 290 |
    291 | } 292 |
    293 |
    294 | 295 | 296 | 297 | {this.renderColumns()} 298 | 299 | 300 | 301 | { 302 | this.state.options.total > 0 ? 303 | this.renderData() : 304 | 305 | 306 | 307 | } 308 | 309 |
    {this.state.options.texts.empty}
    310 |
    311 |
    312 |
    313 | { 314 | this.props.pagination ? 315 |
    316 | {this.state.options.texts.showing + ' ' + this.state.options.from + ' ' + this.state.options.texts.to + ' ' + 317 | this.state.options.to + ' ' + this.state.options.texts.of + ' ' + this.state.options.total + 318 | ' ' + this.state.options.texts.entries} 319 |
    : 320 |
    321 | { 322 | this.state.options.total + ' ' + this.state.options.texts.entries 323 | } 324 |
    325 | } 326 | { 327 | this.props.pagination && 328 |
      329 | {this.renderPagination()} 330 |
    331 | } 332 |
    333 |
    334 | ); 335 | } 336 | } 337 | 338 | ServerTable.defaultProps = { 339 | options: { 340 | headings: {}, 341 | sortable: [], 342 | columnsWidth: {}, 343 | columnsAlign: {}, 344 | initialPage: 1, 345 | perPage: 10, 346 | perPageValues: [10, 20, 25, 100], 347 | icons: { 348 | sortBase: 'fa fa-sort', 349 | sortUp: 'fa fa-sort-amount-up', 350 | sortDown: 'fa fa-sort-amount-down', 351 | search: 'fa fa-search' 352 | }, 353 | texts: { 354 | show: 'Show', 355 | entries: 'entries', 356 | showing: 'Showing', 357 | to: 'to', 358 | of: 'of', 359 | search: 'Search', 360 | empty: 'Empty Results' 361 | }, 362 | requestParametersNames: { 363 | query: 'query', 364 | limit: 'limit', 365 | page: 'page', 366 | orderBy: 'orderBy', 367 | direction: 'direction', 368 | }, 369 | orderDirectionValues: { 370 | ascending: 'asc', 371 | descending: 'desc', 372 | }, 373 | total: 10, 374 | currentPage: 1, 375 | lastPage: 1, 376 | from: 1, 377 | to: 1, 378 | loading: ( 379 |
    Loading... 381 |
    ), 382 | responseAdapter: function (resp_data) { 383 | return {data: resp_data.data, total: resp_data.total} 384 | }, 385 | maxHeightTable: 'unset' 386 | }, 387 | perPage: true, 388 | search: true, 389 | pagination: true, 390 | updateUrl: false, 391 | }; 392 | 393 | ServerTable.propTypes = { 394 | columns: PropTypes.array.isRequired, 395 | url: PropTypes.string.isRequired, 396 | 397 | hover: PropTypes.bool, 398 | bordered: PropTypes.bool, 399 | condensed: PropTypes.bool, 400 | striped: PropTypes.bool, 401 | perPage: PropTypes.bool, 402 | search: PropTypes.bool, 403 | pagination: PropTypes.bool, 404 | updateUrl: PropTypes.bool, 405 | 406 | options: PropTypes.object, 407 | children: PropTypes.func, 408 | }; 409 | 410 | 411 | export default ServerTable; 412 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | .react-strap-table .card-header label { 2 | margin-bottom: 0; 3 | } 4 | 5 | .react-strap-table .table-sort-th { 6 | cursor: pointer; 7 | } 8 | 9 | .react-strap-table .table-sort-icon { 10 | float: right; 11 | } 12 | 13 | .react-strap-table .card-header { 14 | background-color: rgba(0, 0, 0, 0); 15 | } 16 | 17 | .react-strap-table .card-footer { 18 | background-color: rgba(0, 0, 0, 0); 19 | padding-top: 0px; 20 | border-top: 0px; 21 | } 22 | 23 | .react-strap-table .card-body { 24 | padding-bottom: 0px; 25 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const htmlWebpackPlugin = new HtmlWebpackPlugin({ 4 | template: path.join(__dirname, "examples/src/index.html"), 5 | filename: "./index.html", 6 | hash: true, 7 | chunks: ['index'], 8 | title: 'React Strap Table', 9 | myPageHeader: 'Simple Example', 10 | }); 11 | const htmlWebpackPlugin2 = new HtmlWebpackPlugin({ 12 | template: path.join(__dirname, "examples/src/advance.html"), 13 | filename: "./advance.html", 14 | hash: true, 15 | chunks: ['advance'], 16 | title: 'React Strap Table', 17 | myPageHeader: 'Advance Example', 18 | }); 19 | module.exports = { 20 | entry: { 21 | index: path.join(__dirname, "examples/src/index.js"), 22 | advance: path.join(__dirname, "examples/src/advance.js") 23 | }, 24 | output: { 25 | path: path.join(__dirname, "examples/dist"), 26 | filename: "[name].bundle.js" 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|jsx)$/, 32 | use: "babel-loader", 33 | exclude: /node_modules/ 34 | }, 35 | { 36 | test: /\.css$/, 37 | use: ["style-loader", "css-loader"] 38 | } 39 | ] 40 | }, 41 | plugins: [htmlWebpackPlugin, htmlWebpackPlugin2], 42 | resolve: { 43 | extensions: [".js", ".jsx"] 44 | }, 45 | devServer: { 46 | port: 3001 47 | } 48 | }; --------------------------------------------------------------------------------