├── .babelrc ├── .eslintrc.json ├── .gitignore ├── .mochasetup.js ├── .travis.yml ├── LICENSE ├── README.md ├── example ├── app │ ├── index.html │ └── main.css └── src │ ├── Main.js │ └── app.js ├── gulpfile.js ├── package.json ├── src ├── DataTables │ ├── DataTables.js │ ├── DataTablesHeaderColumn.js │ ├── DataTablesHeaderToolbar.js │ ├── DataTablesRow.js │ ├── DataTablesRowColumn.js │ ├── DataTablesTable.js │ ├── DataTablesTableBody.js │ └── index.js └── index.js ├── test └── DataTables │ ├── DataTables.spec.js │ └── tableSettings.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 7, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "globals": { 18 | "React": true 19 | }, 20 | "plugins": [ 21 | "babel", 22 | "react" 23 | ], 24 | "extends": [ 25 | "eslint:recommended" 26 | ], 27 | "rules": { 28 | "array-bracket-spacing": ["error", "never"], 29 | "arrow-spacing": "error", 30 | "arrow-parens": "error", 31 | "brace-style": "error", 32 | "comma-dangle": ["error", "always-multiline"], 33 | "comma-spacing": ["error", {"before": false, "after": true}], 34 | "comma-style": ["error", "last"], 35 | "computed-property-spacing": ["error", "never"], 36 | "consistent-this": ["error", "self"], 37 | "consistent-return": "off", 38 | "dot-notation": "error", 39 | "dot-location": ["error", "property"], 40 | "eqeqeq": ["error", "smart"], 41 | "eol-last": "error", 42 | "indent": ["error", 2, {"SwitchCase": 1}], 43 | "id-blacklist": ["error", "e"], 44 | "jsx-quotes": ["error", "prefer-double"], 45 | "keyword-spacing": "error", 46 | "key-spacing": "error", 47 | "max-len": ["error", 120, 4], 48 | "new-cap": ["off", {"capIsNew": true, "newIsCap": true}], 49 | "no-unused-expressions": "error", 50 | "no-unused-vars": "error", 51 | "no-shadow": "off", 52 | "no-spaced-func": "error", 53 | "no-multiple-empty-lines": "error", 54 | "no-multi-spaces": "error", 55 | "no-undef": "error", 56 | "no-empty-pattern": "error", 57 | "no-dupe-keys": "error", 58 | "no-dupe-args": "error", 59 | "no-duplicate-case": "error", 60 | "no-cond-assign": "error", 61 | "no-extra-semi": "error", 62 | "no-extra-boolean-cast": "error", 63 | "no-trailing-spaces": "error", 64 | "no-underscore-dangle": "error", 65 | "no-unneeded-ternary": "error", 66 | "no-unreachable": "error", 67 | "no-var": "error", 68 | "one-var": ["error", "never"], 69 | "operator-linebreak": ["error", "after"], 70 | "padded-blocks": ["error", "never"], 71 | "prefer-arrow-callback": "off", 72 | "prefer-const": "error", 73 | "prefer-template": "error", 74 | "quotes": ["error", "single", "avoid-escape"], 75 | "semi": ["error", "always"], 76 | "space-before-blocks": ["error", "always"], 77 | "space-before-function-paren": ["error", "never"], 78 | "space-infix-ops": "error", 79 | "space-unary-ops": ["error", {"words": true, "nonwords": false}], 80 | "spaced-comment": "error", 81 | "yoda": "error", 82 | "babel/object-curly-spacing": ["error", "never"], 83 | "babel/generator-star-spacing": "error", 84 | "babel/array-bracket-spacing": "error", 85 | "babel/arrow-parens": "error", 86 | "babel/no-await-in-loop": "error", 87 | "babel/func-params-comma-dangle": "error", 88 | "babel/flow-object-type": "error", 89 | "react/display-name": "error", 90 | "react/jsx-boolean-value": ["error", "always"], 91 | "react/jsx-closing-bracket-location": "error", 92 | "react/jsx-curly-spacing": "error", 93 | "react/jsx-equals-spacing": "error", 94 | "react/jsx-filename-extension": ["error", {"extensions": [".js"]}], 95 | "react/jsx-first-prop-new-line": ["error", "multiline"], 96 | "react/jsx-handler-names": "error", 97 | "react/jsx-indent": ["error", 2], 98 | "react/jsx-indent-props": ["error", 2], 99 | "react/jsx-max-props-per-line": ["error", {"maximum": 3}], 100 | "react/jsx-no-comment-textnodes": "error", 101 | "react/jsx-no-duplicate-props": "error", 102 | "react/jsx-no-undef": "error", 103 | "react/jsx-pascal-case": "error", 104 | "react/jsx-space-before-closing": "error", 105 | "react/jsx-uses-react": "error", 106 | "react/jsx-uses-vars": "error", 107 | "react/jsx-wrap-multilines": "error", 108 | "react/no-danger": "error", 109 | "react/no-deprecated": "error", 110 | "react/no-did-mount-set-state": "error", 111 | "react/no-did-update-set-state": "error", 112 | "react/no-direct-mutation-state": "error", 113 | "react/no-multi-comp": "off", 114 | "react/no-render-return-value": "error", 115 | "react/no-is-mounted": "error", 116 | "react/no-unknown-property": "error", 117 | "react/prefer-arrow-callback": "off", 118 | "react/prefer-es6-class": "error", 119 | "react/prop-types": "error", 120 | "react/react-in-jsx-scope": "error", 121 | "react/require-extension": "error", 122 | "react/require-render-return": "error", 123 | "react/self-closing-comp": "error", 124 | "react/sort-comp": "error", 125 | "react/sort-prop-types": "error", 126 | "react/no-string-refs": "warn", 127 | 128 | "strict": "off", 129 | "no-case-declarations": "off", 130 | "react/jsx-key": "off", 131 | "react/jsx-no-bind": "off", 132 | "react/jsx-no-literals": "off", 133 | "react/jsx-no-target-blank": "off", 134 | "react/jsx-sort-props": "off", 135 | "react/no-set-state": "off", 136 | "react/forbid-prop-types": "off", 137 | "react/prefer-stateless-function": "off", 138 | "react/require-optimization": "off", 139 | "babel/object-shorthand": "off", 140 | "babel/new-cap": "off" 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | example/app/bundle.js 5 | build 6 | coverage/ 7 | .nyc_output 8 | ### Node template 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (http://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # Typescript v1 declaration files 48 | typings/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | 68 | ### Example user template template 69 | ### Example user template 70 | 71 | # IntelliJ project files 72 | .idea 73 | *.iml 74 | out 75 | gen 76 | -------------------------------------------------------------------------------- /.mochasetup.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(); 2 | 3 | var jsdom = require('jsdom').jsdom; 4 | 5 | var exposedProperties = ['window', 'navigator', 'document']; 6 | 7 | global.document = jsdom(''); 8 | global.window = document.defaultView; 9 | Object.keys(document.defaultView).forEach((property) => { 10 | if (typeof global[property] === 'undefined') { 11 | exposedProperties.push(property); 12 | global[property] = document.defaultView[property]; 13 | } 14 | }); 15 | 16 | global.navigator = { 17 | userAgent: 'node.js' 18 | }; 19 | 20 | documentRef = document; 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | before_script: 5 | - npm install -g gulp 6 | script: 7 | - npm run lint & npm test 8 | after_success: 9 | - npm run test:coveralls 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Hyojin Kwak 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 | # Material-UI-Datatables 2 | 3 | [![npm](https://img.shields.io/npm/v/material-ui-datatables.svg?style=flat-square)](https://www.npmjs.com/package/material-ui-datatables) 4 | [![Build Status](https://img.shields.io/travis/hyojin/material-ui-datatables/master.svg?style=flat-square)](https://travis-ci.org/hyojin/material-ui-datatables) 5 | [![Coverage Status](https://img.shields.io/coveralls/hyojin/material-ui-datatables/master.svg?style=flat-square)](https://coveralls.io/github/hyojin/material-ui-datatables?branch=master) 6 | 7 | An another React Data tables component. 8 | Material-UI-Datatables is a custom [React](https://facebook.github.io/react/) component using awesome [Material-UI](http://www.material-ui.com/). It provides rendering data and emitting events 9 | such as filter and column sort and pagination which may help you dealing with your data. But it doesn't provide features all done within the component. Most parts of this component are stateless, which means you need to implement your logic for the events. 10 | 11 | **Now material-ui provides [example code](https://material-ui-1dab0.firebaseapp.com/demos/tables/) of data tables component with it's v1.0.0 package** 12 | 13 | ## Installation 14 | ```sh 15 | npm install material-ui-datatables 16 | ``` 17 | or 18 | ```sh 19 | yarn add material-ui-datatables 20 | ``` 21 | 22 | ## Demo 23 | [Demo](https://hyojin.github.io/material-ui-datatables/) 24 | 25 | ## Status 26 | Work in progress 27 | 28 | ## Usage 29 | ```jsx 30 | import React, {Component} from 'react'; 31 | import DataTables from 'material-ui-datatables'; 32 | 33 | const TABLE_COLUMNS = [ 34 | { 35 | key: 'name', 36 | label: 'Dessert (100g serving)', 37 | }, { 38 | key: 'calories', 39 | label: 'Calories', 40 | }, 41 | ... 42 | ]; 43 | 44 | const TABLE_DATA = [ 45 | { 46 | name: 'Frozen yogurt', 47 | calories: '159', 48 | fat: '6.0', 49 | carbs: '24', 50 | ... 51 | }, { 52 | name: 'Ice cream sandwich', 53 | calories: '159', 54 | fat: '6.0', 55 | carbs: '24', 56 | ... 57 | }, 58 | ... 59 | ]; 60 | 61 | class MyComponent extends Component { 62 | ... 63 | handleFilterValueChange = (value) => { 64 | // your filter logic 65 | } 66 | 67 | handleSortOrderChange = (key, order) => { 68 | // your sort logic 69 | } 70 | 71 | render() { 72 | return ( 73 | 87 | ); 88 | } 89 | } 90 | ``` 91 | 92 | ## Properties 93 | | Name | Type | Default | Description | 94 | |----------------------|-----------|-------------------|----------------------------------------------| 95 | | columns | array | | Array of column settings [object](https://github.com/hyojin/material-ui-datatables#column-settings) | 96 | | count | number | 0 | | 97 | | data | array | | | 98 | | enableSelectAll | bool | false | | 99 | | filterHintText | string | 'Search' | | 100 | | filterValue | string | '' | | 101 | | footerToolbarStyle | object | | | 102 | | headerToolbarMode | string | 'default' | 'default' or 'filter' | 103 | | height | string | 'inherit' | | 104 | | initialSort | object | | {column: 'column key', order: 'asc or desc'} | 105 | | multiSelectable | bool | false | | 106 | | onCellClick | function | | | 107 | | onCellDoubleClick | function | | | 108 | | onFilterValueChange | function | | Should set 'showHeaderToolbar' to true first | 109 | | onNextPageClick | function | | | 110 | | onPreviousPageClick | function | | | 111 | | onRowSelection | function | | | 112 | | onRowSizeChange | function | | | 113 | | onSortOrderChange | function | | | 114 | | page | number | 1 | | 115 | | rowSize | number | 10 | | 116 | | rowSizeLabel | string | 'Rows per page:' | | 117 | | rowSizeList | array | [10, 30, 50, 100] | | 118 | | selectable | bool | false | | 119 | | selectedRows | array | [] | | 120 | | showCheckboxes | bool | false | | 121 | | showFooterToolbar | bool | true | | 122 | | showHeaderToolbar | bool | false | | 123 | | showHeaderToolbarFilterIcon | bool | true | | 124 | | showRowHover | bool | false | | 125 | | showRowSizeControls | bool | false | | 126 | | summaryLabelTemplate | function | | | 127 | | tableBodyStyle | object | | | 128 | | tableHeaderColumnStyle | object | | | 129 | | tableHeaderStyle | object | | | 130 | | tableRowColumnStyle | object | | | 131 | | tableRowStyle | object | | | 132 | | tableStyle | object | | | 133 | | tableWrapperStyle | object | | | 134 | | title | string | | Should set 'showHeaderToolbar' to true first | 135 | | titleStyle | object | | Should set 'showHeaderToolbar' to true first | 136 | | toolbarIconRight | node | | Can be an array of IconButton nodes | 137 | 138 | ## Column settings 139 | | Name | Type | Default | Description | 140 | |----------------------|-----------|-------------------|----------------------------------------------| 141 | | key | string | | | 142 | | label | string | | | 143 | | sortable | bool | false | | 144 | | tooltip | string | | | 145 | | className | string | | | 146 | | render | function | | | 147 | | alignRight | bool | | | 148 | | style | object | | Inline column styles | 149 | 150 | ### Setting example 151 | ```javascript 152 | { 153 | key: 'name', 154 | label: 'Dessert (100g serving)', 155 | sortable: true, 156 | tooltip: 'Dessert (100g serving)', 157 | className: 'important-column', 158 | style: { 159 | width: 250, 160 | }, 161 | render: (name, all) =>

{name}

162 | } 163 | ``` 164 | 165 | ## License 166 | MIT 167 | -------------------------------------------------------------------------------- /example/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Material-UI Custom Components Example 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/app/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: 'Roboto', sans-serif; 3 | } 4 | 5 | body { 6 | font-size: 13px; 7 | line-height: 20px; 8 | } 9 | -------------------------------------------------------------------------------- /example/src/Main.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import RaisedButton from 'material-ui/RaisedButton'; 3 | import Dialog from 'material-ui/Dialog'; 4 | import {deepOrange500} from 'material-ui/styles/colors'; 5 | import FlatButton from 'material-ui/FlatButton'; 6 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 7 | import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; 8 | import MenuItem from 'material-ui/MenuItem'; 9 | import {Card, CardHeader} from 'material-ui/Card'; 10 | import IconButton from 'material-ui/IconButton'; 11 | import PersonAdd from 'material-ui/svg-icons/social/person-add'; 12 | import InfoOutline from 'material-ui/svg-icons/action/info-outline'; 13 | 14 | import DataTables from '../../src'; 15 | 16 | const styles = { 17 | container: { 18 | textAlign: 'center', 19 | }, 20 | component: { 21 | margin: '60px 20px', 22 | }, 23 | titleStyle: { 24 | fontSize: 16, 25 | color: deepOrange500, 26 | }, 27 | footerToolbarStyle: { 28 | padding: '0 100px', 29 | }, 30 | tableStyle: { 31 | tableLayout: 'auto', 32 | }, 33 | tableBodyStyle: { 34 | overflowX: 'auto', 35 | }, 36 | tableWrapperStyle: { 37 | padding: 5, 38 | }, 39 | }; 40 | 41 | const muiTheme = getMuiTheme({ 42 | palette: { 43 | accent1Color: deepOrange500, 44 | }, 45 | }); 46 | 47 | const TABLE_COLUMNS = [ 48 | { 49 | key: 'name', 50 | label: 'Dessert (100g serving)', 51 | }, { 52 | key: 'calories', 53 | label: 'Calories', 54 | }, { 55 | key: 'fat', 56 | label: 'Fat (g)', 57 | }, { 58 | key: 'carbs', 59 | label: 'Carbs (g)', 60 | }, { 61 | key: 'protein', 62 | label: 'Protein (g)', 63 | }, { 64 | key: 'sodium', 65 | label: 'Sodium (mg)', 66 | }, { 67 | key: 'calcium', 68 | label: 'Calcium (%)', 69 | }, { 70 | key: 'iron', 71 | label: 'Iron (%)', 72 | }, 73 | ]; 74 | 75 | const TABLE_COLUMNS_TOOLTIP = [ 76 | { 77 | key: 'name', 78 | label: 'Dessert (100g serving)', 79 | tooltip: 'Dessert (100g serving)', 80 | }, { 81 | key: 'calories', 82 | label: 'Calories', 83 | tooltip: 'Calories', 84 | }, { 85 | key: 'fat', 86 | label: 'Fat (g)', 87 | tooltip: 'Fat (g)', 88 | }, { 89 | key: 'carbs', 90 | label: 'Carbs (g)', 91 | tooltip: 'Carbs (g)', 92 | }, { 93 | key: 'protein', 94 | label: 'Protein (g)', 95 | tooltip: 'Protein (g)', 96 | }, { 97 | key: 'sodium', 98 | label: 'Sodium (mg)', 99 | tooltip: 'Sodium (mg)', 100 | }, { 101 | key: 'calcium', 102 | label: 'Calcium (%)', 103 | tooltip: 'Calcium (%)', 104 | }, { 105 | key: 'iron', 106 | label: 'Iron (%)', 107 | tooltip: 'Iron (%)', 108 | }, 109 | ]; 110 | 111 | const TABLE_COLUMNS_SORT_STYLE = [ 112 | { 113 | key: 'name', 114 | label: 'Dessert (100g serving)', 115 | sortable: true, 116 | style: { 117 | width: 250, 118 | } 119 | }, { 120 | key: 'calories', 121 | label: 'Calories', 122 | sortable: true, 123 | }, { 124 | key: 'fat', 125 | label: 'Fat (g)', 126 | alignRight: true, 127 | }, { 128 | key: 'carbs', 129 | label: 'Carbs (g)', 130 | }, { 131 | key: 'protein', 132 | label: 'Protein (g)', 133 | }, { 134 | key: 'sodium', 135 | label: 'Sodium (mg)', 136 | }, { 137 | key: 'calcium', 138 | label: 'Calcium (%)', 139 | }, { 140 | key: 'iron', 141 | label: 'Iron (%)', 142 | }, 143 | ]; 144 | 145 | const TABLE_COLUMNS_CLASSNAME = [ 146 | { 147 | key: 'name', 148 | label: 'Dessert (100g serving)', 149 | className: 'important-column', 150 | }, { 151 | key: 'calories', 152 | label: 'Calories', 153 | className: 'important-column', 154 | }, { 155 | key: 'fat', 156 | label: 'Fat (g)', 157 | }, { 158 | key: 'carbs', 159 | label: 'Carbs (g)', 160 | }, { 161 | key: 'protein', 162 | label: 'Protein (g)', 163 | }, { 164 | key: 'sodium', 165 | label: 'Sodium (mg)', 166 | }, { 167 | key: 'calcium', 168 | label: 'Calcium (%)', 169 | }, { 170 | key: 'iron', 171 | label: 'Iron (%)', 172 | }, 173 | ]; 174 | 175 | const TABLE_DATA = [ 176 | { 177 | name: 'Frozen yogurt', 178 | calories: '159', 179 | fat: '6.0', 180 | carbs: '24', 181 | protein: '4.0', 182 | sodium: '87', 183 | calcium: '14%', 184 | iron: '1%', 185 | }, { 186 | name: 'Ice cream sandwich', 187 | calories: '159', 188 | fat: '6.0', 189 | carbs: '24', 190 | protein: '4.0', 191 | sodium: '87', 192 | calcium: '14%', 193 | iron: '1%', 194 | }, { 195 | name: 'Eclair', 196 | calories: '159', 197 | fat: '6.0', 198 | carbs: '24', 199 | protein: '4.0', 200 | sodium: '87', 201 | calcium: '14%', 202 | iron: '1%', 203 | }, { 204 | name: 'Cupcake', 205 | calories: '159', 206 | fat: '6.0', 207 | carbs: '24', 208 | protein: '4.0', 209 | sodium: '87', 210 | calcium: '14%', 211 | iron: '1%', 212 | }, { 213 | name: 'Gingerbread', 214 | calories: '159', 215 | fat: '6.0', 216 | carbs: '24', 217 | protein: '4.0', 218 | sodium: '87', 219 | calcium: '14%', 220 | iron: '1%', 221 | }, { 222 | name: 'Jelly bean', 223 | calories: '159', 224 | fat: '6.0', 225 | carbs: '24', 226 | protein: '4.0', 227 | sodium: '87', 228 | calcium: '14%', 229 | iron: '1%', 230 | }, { 231 | name: 'Lollipop', 232 | calories: '159', 233 | fat: '6.0', 234 | carbs: '24', 235 | protein: '4.0', 236 | sodium: '87', 237 | calcium: '14%', 238 | iron: '1%', 239 | }, { 240 | name: 'Honeycomb', 241 | calories: '159', 242 | fat: '6.0', 243 | carbs: '24', 244 | protein: '4.0', 245 | sodium: '87', 246 | calcium: '14%', 247 | iron: '1%', 248 | }, { 249 | name: 'Donut', 250 | calories: '159', 251 | fat: '6.0', 252 | carbs: '24', 253 | protein: '4.0', 254 | sodium: '87', 255 | calcium: '14%', 256 | iron: '1%', 257 | }, { 258 | name: 'KitKat', 259 | calories: '159', 260 | fat: '6.0', 261 | carbs: '24', 262 | protein: '4.0', 263 | sodium: '87', 264 | calcium: '14%', 265 | iron: '1%', 266 | }, 267 | ]; 268 | 269 | const TABLE_DATA_NEXT = [ 270 | { 271 | name: 'Marshmallow', 272 | calories: '159', 273 | fat: '6.0', 274 | carbs: '24', 275 | protein: '4.0', 276 | sodium: '87', 277 | calcium: '14%', 278 | iron: '1%', 279 | }, 280 | ]; 281 | 282 | class Main extends Component { 283 | constructor(props, context) { 284 | super(props, context); 285 | this.handleSortOrderChange = this.handleSortOrderChange.bind(this); 286 | this.handleFilterValueChange = this.handleFilterValueChange.bind(this); 287 | this.handleCellClick = this.handleCellClick.bind(this); 288 | this.handleCellDoubleClick = this.handleCellDoubleClick.bind(this); 289 | this.handleRowSelection = this.handleRowSelection.bind(this); 290 | this.handlePreviousPageClick = this.handlePreviousPageClick.bind(this); 291 | this.handleNextPageClick = this.handleNextPageClick.bind(this); 292 | this.handlePersonAddClick = this.handlePersonAddClick.bind(this); 293 | this.handleInfoClick = this.handleInfoClick.bind(this); 294 | 295 | this.state = { 296 | data: TABLE_DATA, 297 | page: 1, 298 | }; 299 | } 300 | 301 | handleSortOrderChange(key, order) { 302 | console.log('key:' + key + ' order: ' + order); 303 | } 304 | 305 | handleFilterValueChange(value) { 306 | console.log('filter value: ' + value); 307 | } 308 | 309 | handleCellClick(rowIndex, columnIndex, row, column) { 310 | console.log('rowIndex: ' + rowIndex + ' columnIndex: ' + columnIndex); 311 | } 312 | 313 | handleCellDoubleClick(rowIndex, columnIndex, row, column) { 314 | console.log('rowIndex: ' + rowIndex + ' columnIndex: ' + columnIndex); 315 | } 316 | 317 | handleRowSelection(selectedRows) { 318 | console.log('selectedRows: ' + selectedRows); 319 | } 320 | 321 | handlePreviousPageClick() { 322 | console.log('handlePreviousPageClick'); 323 | this.setState({ 324 | data: TABLE_DATA, 325 | page: 1, 326 | }); 327 | } 328 | 329 | handleNextPageClick() { 330 | console.log('handleNextPageClick'); 331 | this.setState({ 332 | data: TABLE_DATA_NEXT, 333 | page: 2, 334 | }); 335 | } 336 | 337 | handlePersonAddClick() { 338 | console.log('handlePersonAddClick'); 339 | } 340 | 341 | handleInfoClick() { 342 | console.log('handleInfoClick'); 343 | } 344 | 345 | render() { 346 | return ( 347 | 348 |
349 |

Material-UI-Custom-Components

350 |
351 |

DataTables (Basic)

352 | 353 | 357 | 366 | 367 |
368 |
369 |

DataTables (Selectable & Tooltip & Pagination)

370 | 371 | 375 | 390 | 391 |
392 |
393 |

DataTables (Filter & Column Sort & Styled Column)

394 | 395 | 410 | 411 |
412 |
413 |

DataTables (Internationalization)

414 | 415 | {return `${start} - ${end} ${count}件`}} 425 | showCheckboxes={false} 426 | showHeaderToolbar={true} 427 | count={100} 428 | /> 429 | 430 |
431 |
432 |

DataTables (Toolbar Icons & Styled title & Styled table)

433 | 434 | 453 | 454 | , 455 | 458 | 459 | 460 | ]} 461 | /> 462 | 463 |
464 |
465 |

DataTables (Column class name)

466 | 467 | 471 | 480 | 481 |
482 |
483 |

DataTables (Programmatically select rows)

484 | 485 | 489 | 500 | 501 |
502 |
503 |

DataTables (onRowSelection handler & onCellDoubleClick handler)

504 | 505 | 509 | 523 | 524 |
525 |
526 |

DataTables (Disable footer toolbar)

527 | 528 | 532 | 544 | 545 |
546 |
547 |

DataTables (Header toolbar mode)

548 | 549 | 565 | 566 |
567 |
568 |

DataTables (Hide filter icon)

569 | 570 | 585 | 586 |
587 |
588 |
589 | ); 590 | } 591 | } 592 | 593 | export default Main; 594 | -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render} from 'react-dom'; 3 | import injectTapEventPlugin from 'react-tap-event-plugin'; 4 | import Main from './Main'; 5 | 6 | injectTapEventPlugin(); 7 | 8 | render(
, document.getElementById('root')); 9 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const browserify = require('browserify'); 3 | const watchify = require('watchify'); 4 | const babelify = require('babelify'); 5 | const source = require('vinyl-source-stream'); 6 | const eslint = require('gulp-eslint'); 7 | const browserSync = require('browser-sync').create(); 8 | const babel = require('gulp-babel'); 9 | const del = require('del'); 10 | const copy = require('gulp-copy'); 11 | const mocha = require('gulp-mocha'); 12 | 13 | function map_error(err) { 14 | console.log('Error : ' + err.message); 15 | this.emit('end'); 16 | }; 17 | 18 | function bundle_js(bundler) { 19 | return bundler.bundle() 20 | .on('error', map_error) 21 | .pipe(source('bundle.js')) 22 | .pipe(gulp.dest('./example/app')); 23 | }; 24 | 25 | gulp.task('watchify', () => { 26 | const bundler = watchify(browserify('./example/src/app.js', Object.assign(watchify.args, { debug: true })) 27 | .transform('babelify', { presets: ['es2015', 'react', 'stage-1'] }), { verbose: true }); 28 | bundle_js(bundler); 29 | bundler.on('update', () => { 30 | bundle_js(bundler); 31 | }); 32 | bundler.on('log', (msg) => { 33 | console.log(msg); 34 | }); 35 | }); 36 | 37 | gulp.task('browserSync', function() { 38 | browserSync.init({ 39 | notify: false, 40 | port: 3000, 41 | open: true, 42 | server: { 43 | baseDir: ['example/app'] 44 | }, 45 | }); 46 | gulp.watch('example/app/bundle.js').on('change', browserSync.reload); 47 | }); 48 | 49 | gulp.task('eslint', function() { 50 | return gulp.src(['src/**/*.js']) 51 | .pipe(eslint()) 52 | .pipe(eslint.format()) 53 | .pipe(eslint.failAfterError()); 54 | }); 55 | 56 | gulp.task('build:clean', function() { 57 | del.sync(['build']); 58 | }); 59 | 60 | gulp.task('build', () => { 61 | return gulp.src('src/**/*.js') 62 | .pipe(babel({ 63 | presets: ['es2015', 'stage-1', 'react'], 64 | plugins: ['transform-runtime'] 65 | })) 66 | .pipe(gulp.dest('build/')); 67 | }); 68 | 69 | gulp.task('build:copy', function() { 70 | return gulp.src(['package.json', 'README.md', 'LICENSE']) 71 | .pipe(copy('build/', {prefix: 1})); 72 | }); 73 | 74 | gulp.task('test', function() { 75 | return gulp.src(['.mochasetup.js', 'test/**/*.spec.js']) 76 | .pipe(babel({ 77 | presets: ['es2015', 'stage-1', 'react'], 78 | plugins: ['transform-runtime'] 79 | })) 80 | .pipe(mocha({ 81 | reporter: 'spec', 82 | })); 83 | }); 84 | 85 | gulp.task('clean:build', ['build:clean', 'build', 'build:copy']); 86 | 87 | gulp.task('default', ['watchify', 'browserSync']); 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "material-ui-datatables", 3 | "version": "0.18.2", 4 | "description": "An another React Data tables component.", 5 | "main": "./src/index.js", 6 | "scripts": { 7 | "start": "gulp", 8 | "test": "gulp test", 9 | "test:coveralls": "nyc npm test && nyc report --reporter=text-lcov | coveralls", 10 | "lint": "gulp eslint", 11 | "clean:build": "gulp clean:build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/hyojin/material-ui-datatables.git" 16 | }, 17 | "keywords": [ 18 | "react", 19 | "react-component", 20 | "material design", 21 | "material-ui", 22 | "datatables" 23 | ], 24 | "author": "Hyojin Kwak", 25 | "license": "MIT", 26 | "peerDependencies": { 27 | "react": "^15.5.4", 28 | "react-dom": "^15.5.4", 29 | "react-tap-event-plugin": "^1.0.0 || ^2.0.0" 30 | }, 31 | "dependencies": { 32 | "babel-runtime": "^6.18.0", 33 | "material-ui": "0.18.0", 34 | "prop-types": "^15.5.10" 35 | }, 36 | "devDependencies": { 37 | "babel-core": "^6.18.2", 38 | "babel-eslint": "^7.1.0", 39 | "babel-loader": "^6.2.7", 40 | "babel-plugin-transform-runtime": "^6.15.0", 41 | "babel-preset-es2015": "^6.16.0", 42 | "babel-preset-react": "^6.16.0", 43 | "babel-preset-stage-1": "^6.16.0", 44 | "babelify": "^7.3.0", 45 | "browser-sync": "^2.17.5", 46 | "browserify": "^13.1.0", 47 | "chai": "^3.5.0", 48 | "coveralls": "^2.11.16", 49 | "del": "^2.2.2", 50 | "enzyme": "^2.8.2", 51 | "eslint": "^3.9.1", 52 | "eslint-plugin-babel": "^3.3.0", 53 | "eslint-plugin-material-ui": "^1.0.1", 54 | "eslint-plugin-react": "^6.6.0", 55 | "gulp": "^3.9.1", 56 | "gulp-babel": "^6.1.2", 57 | "gulp-copy": "0.0.2", 58 | "gulp-eslint": "^3.0.1", 59 | "gulp-load-plugins": "^1.3.0", 60 | "gulp-mocha": "^3.0.1", 61 | "jsdom": "^9.10.0", 62 | "mocha-lcov-reporter": "^1.2.0", 63 | "nyc": "^10.1.2", 64 | "react": "^15.5.4", 65 | "react-addons-test-utils": "^15.4.2", 66 | "react-dom": "^15.5.4", 67 | "react-tap-event-plugin": "^2.0.0", 68 | "sinon": "^1.17.7", 69 | "vinyl-source-stream": "^1.1.0", 70 | "watchify": "^3.7.0", 71 | "webpack": "^1.13.3" 72 | }, 73 | "browserify": { 74 | "transform": [["babelify", {"presets": ["es2015", "react", "stage-1"]}]] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/DataTables/DataTables.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {TableHeader, TableRow} from 'material-ui/Table'; 4 | import {Toolbar} from 'material-ui/Toolbar'; 5 | import DropDownMenu from 'material-ui/DropDownMenu'; 6 | import MenuItem from 'material-ui/MenuItem'; 7 | import FlatButton from 'material-ui/FlatButton'; 8 | import ChevronLeft from 'material-ui/svg-icons/navigation/chevron-left'; 9 | import ChevronRight from 'material-ui/svg-icons/navigation/chevron-right'; 10 | // customized components 11 | import DataTablesTable from './DataTablesTable'; 12 | import DataTablesTableBody from './DataTablesTableBody'; 13 | import DataTablesHeaderColumn from './DataTablesHeaderColumn'; 14 | import DataTablesRow from './DataTablesRow'; 15 | import DataTablesRowColumn from './DataTablesRowColumn'; 16 | import DataTablesHeaderToolbar from './DataTablesHeaderToolbar'; 17 | 18 | function getStyles(props, context) { 19 | const { 20 | baseTheme: { 21 | palette, 22 | }, 23 | table, 24 | tableHeaderColumn, 25 | } = context.muiTheme; 26 | 27 | return { 28 | tableHeaderColumn: { 29 | fontWeight: 600, 30 | }, 31 | footerToolbar: { 32 | backgroundColor: table.backgroundColor, 33 | borderTop: `1px solid ${palette.borderColor}`, 34 | }, 35 | footerControlGroup: { 36 | fontSize: 12, 37 | color: tableHeaderColumn.textColor, 38 | marginLeft: 'auto', 39 | display: 'flex', 40 | }, 41 | footerToolbarItem: { 42 | marginLeft: 8, 43 | marginRight: 8, 44 | alignItems: 'center', 45 | display: 'flex', 46 | }, 47 | paginationButtons: { 48 | marginLeft: 24, 49 | }, 50 | paginationButton: { 51 | minWidth: 36, 52 | opacity: 0.54, 53 | }, 54 | rowSizeMenu: { 55 | color: tableHeaderColumn.textColor, 56 | }, 57 | rowSizeControlsWrapper: { 58 | display: 'flex', 59 | }, 60 | }; 61 | } 62 | 63 | function isRowSelected(index, selectedRows) { 64 | if (Array.isArray(selectedRows)) { 65 | return selectedRows.includes(index); 66 | } else { 67 | return undefined; 68 | } 69 | } 70 | 71 | class DataTables extends Component { 72 | static muiName = 'DataTables'; 73 | 74 | static propTypes = { 75 | columns: PropTypes.array.isRequired, 76 | count: PropTypes.number, 77 | data: PropTypes.array, 78 | deselectOnClickaway: PropTypes.bool, 79 | enableSelectAll: PropTypes.bool, 80 | filterHintText: PropTypes.string, 81 | filterValue: PropTypes.string, 82 | fixedFooter: PropTypes.bool, 83 | fixedHeader: PropTypes.bool, 84 | footerToolbarStyle: PropTypes.object, 85 | headerToolbarMode: PropTypes.string, 86 | height: PropTypes.string, 87 | initialSort: PropTypes.object, 88 | multiSelectable: PropTypes.bool, 89 | onCellClick: PropTypes.func, 90 | onCellDoubleClick: PropTypes.func, 91 | onFilterValueChange: PropTypes.func, 92 | onNextPageClick: PropTypes.func, 93 | onPreviousPageClick: PropTypes.func, 94 | onRowSelection: PropTypes.func, 95 | onRowSizeChange: PropTypes.func, 96 | onSortOrderChange: PropTypes.func, 97 | page: PropTypes.number, 98 | rowSize: PropTypes.number, 99 | rowSizeLabel: PropTypes.string, 100 | rowSizeList: PropTypes.array, 101 | selectable: PropTypes.bool, 102 | selectedRows: PropTypes.array, 103 | showCheckboxes: PropTypes.bool, 104 | showFooterToolbar: PropTypes.bool, 105 | showHeaderToolbar: PropTypes.bool, 106 | showHeaderToolbarFilterIcon: PropTypes.bool, 107 | showRowHover: PropTypes.bool, 108 | showRowSizeControls: PropTypes.bool, 109 | stripedRows: PropTypes.bool, 110 | summaryLabelTemplate: PropTypes.func, 111 | tableBodyStyle: PropTypes.object, 112 | tableHeaderColumnStyle: PropTypes.object, 113 | tableHeaderStyle: PropTypes.object, 114 | tableRowColumnStyle: PropTypes.object, 115 | tableRowStyle: PropTypes.object, 116 | tableStyle: PropTypes.object, 117 | tableWrapperStyle: PropTypes.object, 118 | title: PropTypes.string, 119 | titleStyle: PropTypes.object, 120 | toolbarIconRight: PropTypes.node, 121 | }; 122 | 123 | static contextTypes = { 124 | muiTheme: PropTypes.object.isRequired, 125 | }; 126 | 127 | static defaultProps = { 128 | rowSize: 10, 129 | rowSizeLabel: 'Rows per page:', 130 | rowSizeList: [10, 30, 50, 100], 131 | summaryLabelTemplate: (start, end, count) => { 132 | return `${start} - ${end} of ${count}`; 133 | }, 134 | showRowSizeControls: true, 135 | filterHintText: 'Search', 136 | columns: [], 137 | data: [], 138 | page: 1, 139 | count: 0, 140 | fixedHeader: false, 141 | fixedFooter: false, 142 | stripedRows: false, 143 | showRowHover: false, 144 | selectable: false, 145 | selectedRows: undefined, 146 | multiSelectable: false, 147 | enableSelectAll: false, 148 | deselectOnClickaway: false, 149 | showCheckboxes: false, 150 | height: 'inherit', 151 | showHeaderToolbar: false, 152 | showFooterToolbar: true, 153 | initialSort: { 154 | column: '', 155 | order: 'asc', 156 | }, 157 | }; 158 | 159 | constructor(props, context) { 160 | super(props, context); 161 | this.state = { 162 | sort: props.initialSort, 163 | }; 164 | } 165 | 166 | handleHeaderColumnClick = (event, rowIndex, columnIndex) => { 167 | const adjustedColumnIndex = columnIndex - 1; 168 | const column = this.props.columns[adjustedColumnIndex]; 169 | if (column && column.sortable) { 170 | const {sort} = this.state; 171 | const {onSortOrderChange} = this.props; 172 | const key = column.key; 173 | const order = sort.column === column.key && sort.order === 'asc' ? 'desc' : 'asc'; 174 | this.setState({ 175 | sort: { 176 | column: key, 177 | order: order, 178 | }, 179 | }); 180 | if (onSortOrderChange) { 181 | onSortOrderChange(key, order); 182 | } 183 | } 184 | } 185 | 186 | handleCellClick = (rowIndex, columnIndex, event) => { 187 | const {onCellClick, selectable} = this.props; 188 | if (onCellClick && !selectable) { 189 | const adjustedColumnIndex = this.props.showCheckboxes ? columnIndex : columnIndex - 1; 190 | onCellClick( 191 | rowIndex, 192 | adjustedColumnIndex, 193 | // row data 194 | this.props.data[rowIndex], 195 | // clicked column 196 | this.props.data[rowIndex][this.props.columns[adjustedColumnIndex].key], 197 | event 198 | ); 199 | } 200 | } 201 | 202 | handleCellDoubleClick = (rowIndex, columnIndex, event) => { 203 | const {onCellDoubleClick} = this.props; 204 | if (onCellDoubleClick) { 205 | const adjustedColumnIndex = this.props.showCheckboxes ? columnIndex : columnIndex - 1; 206 | onCellDoubleClick( 207 | rowIndex, 208 | adjustedColumnIndex, 209 | // row data 210 | this.props.data[rowIndex], 211 | // clicked column 212 | this.props.data[rowIndex][this.props.columns[adjustedColumnIndex].key], 213 | event 214 | ); 215 | } 216 | } 217 | 218 | handleRowSizeChange = (event, index, value) => { 219 | const {onRowSizeChange} = this.props; 220 | if (onRowSizeChange) { 221 | onRowSizeChange(index, value); 222 | } 223 | } 224 | 225 | handlePreviousPageClick = (event) => { 226 | const {onPreviousPageClick} = this.props; 227 | if (onPreviousPageClick) { 228 | onPreviousPageClick(event); 229 | } 230 | } 231 | 232 | handleNextPageClick = (event) => { 233 | const {onNextPageClick} = this.props; 234 | if (onNextPageClick) { 235 | onNextPageClick(event); 236 | } 237 | } 238 | 239 | handleFilterValueChange = (value) => { 240 | const {onFilterValueChange} = this.props; 241 | if (onFilterValueChange) { 242 | onFilterValueChange(value); 243 | } 244 | } 245 | 246 | handleRowSelection = (selectedRows) => { 247 | const {onRowSelection} = this.props; 248 | if (onRowSelection) { 249 | onRowSelection(selectedRows); 250 | } 251 | } 252 | 253 | renderTableRowColumnData = (row, column) => { 254 | if (column.render) return column.render(row[column.key], row); 255 | return row[column.key]; 256 | } 257 | 258 | render() { 259 | const { 260 | title, 261 | titleStyle, 262 | filterHintText, 263 | fixedHeader, 264 | fixedFooter, 265 | footerToolbarStyle, 266 | stripedRows, 267 | showRowHover, 268 | selectable, 269 | multiSelectable, 270 | enableSelectAll, 271 | deselectOnClickaway, 272 | showCheckboxes, 273 | height, 274 | showHeaderToolbar, 275 | showFooterToolbar, 276 | rowSize, 277 | rowSizeLabel, 278 | rowSizeList, 279 | showRowSizeControls, 280 | summaryLabelTemplate, 281 | columns, 282 | data, 283 | page, 284 | toolbarIconRight, 285 | count, 286 | tableStyle, 287 | tableBodyStyle, 288 | tableHeaderColumnStyle, 289 | tableHeaderStyle, 290 | tableRowColumnStyle, 291 | tableRowStyle, 292 | tableWrapperStyle, 293 | headerToolbarMode, 294 | filterValue, 295 | showHeaderToolbarFilterIcon, 296 | ...other, // eslint-disable-line no-unused-vars, comma-dangle 297 | } = this.props; 298 | 299 | const styles = getStyles(this.props, this.context); 300 | 301 | let start = (page - 1) * rowSize + 1; 302 | let end = (page - 1) * rowSize + rowSize; 303 | const totalCount = count === 0 ? data.length : count; 304 | let previousButtonDisabled = page === 1; 305 | let nextButtonDisabled = false; 306 | if (totalCount === 0) { 307 | start = 0; 308 | previousButtonDisabled = true; 309 | } else if (start > totalCount) { 310 | start = 1; 311 | previousButtonDisabled = true; 312 | } 313 | if (end >= totalCount) { 314 | end = totalCount; 315 | nextButtonDisabled = true; 316 | } 317 | 318 | let headerToolbar; 319 | if (showHeaderToolbar) { 320 | headerToolbar = ( 321 | 331 | ); 332 | } 333 | 334 | let rowSizeControls = null; 335 | if (showRowSizeControls) { 336 | rowSizeControls = ( 337 |
338 |
339 |
{rowSizeLabel}
340 |
341 | { 342 | rowSizeList.length > 0 ? 343 | ( 344 | 349 | {rowSizeList.map((rowSize) => { 350 | return ( 351 | 356 | ); 357 | })} 358 | 359 | ) : 360 | null 361 | } 362 |
363 | ); 364 | } 365 | 366 | let footerToolbar; 367 | if (showFooterToolbar) { 368 | footerToolbar = ( 369 | 370 |
371 | {rowSizeControls} 372 |
373 |
{summaryLabelTemplate(start, end, totalCount)}
374 |
375 |
376 | } 378 | style={styles.paginationButton} 379 | onClick={this.handlePreviousPageClick} 380 | disabled={previousButtonDisabled} 381 | /> 382 | } 384 | style={styles.paginationButton} 385 | onClick={this.handleNextPageClick} 386 | disabled={nextButtonDisabled} 387 | /> 388 |
389 |
390 |
391 | ); 392 | } 393 | 394 | return ( 395 |
396 | {headerToolbar} 397 | 410 | 416 | 420 | {columns.map((column, index) => { 421 | const style = Object.assign({}, styles.tableHeaderColumn, tableHeaderColumnStyle, column.style || {}); 422 | const sortable = column.sortable; 423 | const sorted = this.state.sort.column === column.key; 424 | const order = sorted ? this.state.sort.order : 'asc'; 425 | return ( 426 | 436 | {column.label} 437 | 438 | ); 439 | }, this)} 440 | 441 | 442 | 448 | {data.map((row, index) => { 449 | return ( 450 | 455 | {columns.map((column, index) => { 456 | return ( 457 | 462 | {this.renderTableRowColumnData(row, column)} 463 | 464 | ); 465 | })} 466 | 467 | ); 468 | })} 469 | 470 | 471 | {footerToolbar} 472 |
473 | ); 474 | } 475 | } 476 | 477 | export default DataTables; 478 | -------------------------------------------------------------------------------- /src/DataTables/DataTablesHeaderColumn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {TableHeaderColumn} from 'material-ui/Table'; 4 | import Tooltip from 'material-ui/internal/Tooltip'; 5 | import ArrowUpward from 'material-ui/svg-icons/navigation/arrow-upward'; 6 | import ArrowDownward from 'material-ui/svg-icons/navigation/arrow-downward'; 7 | 8 | function getStyles(props, context, state) { 9 | const {tableHeaderColumn} = context.muiTheme; 10 | 11 | return { 12 | root: { 13 | fontWeight: 'normal', 14 | fontSize: 12, 15 | paddingLeft: tableHeaderColumn.spacing, 16 | paddingRight: tableHeaderColumn.spacing, 17 | height: tableHeaderColumn.height, 18 | textAlign: props.alignRight ? 'right' : 'left', 19 | whiteSpace: 'nowrap', 20 | textOverflow: 'ellipsis', 21 | color: props.sorted ? '#3A3A3A' : tableHeaderColumn.textColor, 22 | position: 'relative', 23 | cursor: props.sortable ? 'pointer' : 'default', 24 | }, 25 | tooltip: { 26 | boxSizing: 'border-box', 27 | marginTop: tableHeaderColumn.height / 2, 28 | }, 29 | sortIcon: { 30 | height: '100%', 31 | width: '100%', 32 | opacity: props.sorted ? 0.87 : 0.54, 33 | display: state.sortHovered || props.sorted ? 'inline' : 'none', 34 | }, 35 | iconWrapper: { 36 | display: 'inline-block', 37 | height: 16, 38 | width: 16, 39 | verticalAlign: 'middle', 40 | marginLeft: 8, 41 | marginRight: 8, 42 | }, 43 | titleWrapper: { 44 | display: 'inline-block', 45 | verticalAlign: 'middle', 46 | }, 47 | }; 48 | } 49 | 50 | class DataTablesHeaderColumn extends TableHeaderColumn { 51 | static muiName = 'DataTablesHeaderColumn'; 52 | 53 | static propTypes = { 54 | children: PropTypes.node, 55 | /** 56 | * The css class name of the root element. 57 | */ 58 | className: PropTypes.string, 59 | /** 60 | * Number to identify the header row. This property 61 | * is automatically populated when used with TableHeader. 62 | */ 63 | columnNumber: PropTypes.number, 64 | /** 65 | * @ignore 66 | * Not used here but we need to remove it from the root element. 67 | */ 68 | hoverable: PropTypes.bool, 69 | /** @ignore */ 70 | onClick: PropTypes.func, 71 | /** 72 | * @ignore 73 | * Not used here but we need to remove it from the root element. 74 | */ 75 | onHover: PropTypes.func, 76 | /** 77 | * @ignore 78 | * Not used here but we need to remove it from the root element. 79 | */ 80 | onHoverExit: PropTypes.func, 81 | /** 82 | * Override the inline-styles of the root element. 83 | */ 84 | order: PropTypes.string, 85 | sortable: PropTypes.bool, 86 | style: PropTypes.object, 87 | /** 88 | * The string to supply to the tooltip. If not 89 | * string is supplied no tooltip will be shown. 90 | */ 91 | tooltip: PropTypes.string, 92 | /** 93 | * Additional styling that can be applied to the tooltip. 94 | */ 95 | tooltipStyle: PropTypes.object, 96 | 97 | 98 | }; 99 | 100 | static defaultProps = { 101 | sortable: false, 102 | order: 'asc', 103 | }; 104 | 105 | constructor(props, context) { 106 | super(props, context); 107 | this.state = { 108 | sortHovered: false, 109 | }; 110 | } 111 | 112 | onMouseEnter = () => { 113 | if (this.props.tooltip !== undefined) { 114 | this.setState({hovered: true}); 115 | } 116 | if (this.props.sortable) { 117 | this.setState({sortHovered: true}); 118 | } 119 | }; 120 | 121 | onMouseLeave = () => { 122 | if (this.props.tooltip !== undefined) { 123 | this.setState({hovered: false}); 124 | } 125 | if (this.props.sortable) { 126 | this.setState({sortHovered: false}); 127 | } 128 | }; 129 | 130 | render() { 131 | const { 132 | children, 133 | className, 134 | columnNumber, // eslint-disable-line no-unused-vars 135 | hoverable, // eslint-disable-line no-unused-vars 136 | onClick, // eslint-disable-line no-unused-vars 137 | onHover, // eslint-disable-line no-unused-vars 138 | onHoverExit, // eslint-disable-line no-unused-vars 139 | style, 140 | tooltip, 141 | tooltipStyle, 142 | sortable, 143 | sorted, 144 | order, 145 | alignRight, // eslint-disable-line no-unused-vars 146 | ...other, // eslint-disable-line comma-dangle 147 | } = this.props; 148 | 149 | const {prepareStyles} = this.context.muiTheme; 150 | const styles = getStyles(this.props, this.context, this.state); 151 | 152 | const handlers = { 153 | onMouseEnter: this.onMouseEnter, 154 | onMouseLeave: this.onMouseLeave, 155 | onClick: this.onClick, 156 | }; 157 | 158 | let tooltipNode; 159 | 160 | if (tooltip !== undefined) { 161 | tooltipNode = ( 162 | 167 | ); 168 | } 169 | 170 | let sortIcon; 171 | 172 | if (sorted && order === 'asc') { 173 | sortIcon = (
); 174 | } else if (sorted && order === 'desc') { 175 | sortIcon = (
); 176 | } else if (sortable) { 177 | sortIcon = (
); 178 | } 179 | 180 | let leftSortIcon; 181 | let rightSortIcon; 182 | 183 | if (sortable && styles.root.textAlign === 'left') { 184 | rightSortIcon = sortIcon; 185 | } else if (sortable && styles.root.textAlign === 'right') { 186 | leftSortIcon = sortIcon; 187 | } 188 | 189 | const titleNode = (
{children}
); 190 | 191 | return ( 192 | 198 | {tooltipNode} 199 | {leftSortIcon} 200 | {titleNode} 201 | {rightSortIcon} 202 | 203 | ); 204 | } 205 | } 206 | 207 | export default DataTablesHeaderColumn; 208 | -------------------------------------------------------------------------------- /src/DataTables/DataTablesHeaderToolbar.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {Toolbar, ToolbarGroup, ToolbarTitle} from 'material-ui/Toolbar'; 4 | import IconButton from 'material-ui/IconButton'; 5 | import ClearIcon from 'material-ui/svg-icons/content/clear'; 6 | import FilterListIcon from 'material-ui/svg-icons/content/filter-list'; 7 | import SearchIcon from 'material-ui/svg-icons/action/search'; 8 | import TextField from 'material-ui/TextField'; 9 | import {blue500} from 'material-ui/styles/colors'; 10 | 11 | function getStyles(context) { 12 | const { 13 | table, 14 | } = context.muiTheme; 15 | 16 | return { 17 | headerToolbar: { 18 | backgroundColor: table.backgroundColor, 19 | height: 64, 20 | paddingRight: 8, 21 | }, 22 | icon: { 23 | opacity: 0.64, 24 | }, 25 | headerToolbarSearchIcon: { 26 | marginTop: 12, 27 | }, 28 | headerToolbarIconButton: { 29 | marginTop: 6, 30 | }, 31 | searchToolbarGroup: { 32 | width: '100%', 33 | display: 'flex', 34 | alignItems: 'center', 35 | }, 36 | searchInputTextField: { 37 | marginTop: 6, 38 | marginLeft: 8, 39 | width: '100%', 40 | minWidth: 60, 41 | }, 42 | headerToolbarDefaultIcons: { 43 | display: 'flex', 44 | alignItems: 'center', 45 | }, 46 | toolbarTitle: { 47 | lineHeight: '72px', 48 | }, 49 | }; 50 | } 51 | 52 | class DataTablesHeaderToolbar extends Component { 53 | static muiName = 'DataTablesHeaderToolbar'; 54 | 55 | static propTypes = { 56 | filterHintText: PropTypes.string, 57 | filterValue: PropTypes.string, 58 | handleFilterValueChange: PropTypes.func, 59 | mode: PropTypes.string, 60 | onFilterValueChange: PropTypes.func, 61 | showFilterIcon: PropTypes.bool, 62 | title: PropTypes.string, 63 | titleStyle: PropTypes.object, 64 | toolbarIconRight: PropTypes.node, 65 | }; 66 | 67 | static defaultProps = { 68 | mode: 'default', 69 | filterValue: '', 70 | showFilterIcon: true, 71 | }; 72 | 73 | static contextTypes = { 74 | muiTheme: PropTypes.object.isRequired, 75 | }; 76 | 77 | constructor(props, context) { 78 | super(props, context); 79 | this.filterValueTimer = undefined; 80 | this.filterInput = undefined; 81 | this.state = { 82 | mode: props.mode, 83 | filterValue: props.filterValue, 84 | }; 85 | } 86 | 87 | componentDidUpdate(prevProps, prevState) { 88 | if (prevState.mode === 'default' && this.state.mode === 'filter') { 89 | if (this.filterInput) { 90 | this.filterInput.focus(); 91 | } 92 | } 93 | } 94 | 95 | handleFilterClick = () => { 96 | const mode = this.state.mode === 'default' ? 'filter' : 'default'; 97 | const {filterValue} = this.state; 98 | this.setState({ 99 | mode: mode, 100 | filterValue: '', 101 | }); 102 | if (mode === 'default' && filterValue !== '') { 103 | this.emitFilterValueChange(''); 104 | } 105 | } 106 | 107 | handleClearClick = () => { 108 | const {filterValue} = this.state; 109 | if (filterValue !== '') { 110 | this.setState({ 111 | filterValue: '', 112 | }); 113 | this.emitFilterValueChange(''); 114 | } 115 | } 116 | 117 | handleFilterValueChange = (event) => { 118 | const value = event.target.value; 119 | this.setState({ 120 | filterValue: value, 121 | }); 122 | clearTimeout(this.filterValueTimer); 123 | this.filterValueTimer = setTimeout(() => { 124 | this.emitFilterValueChange(value); 125 | }, 500); 126 | } 127 | 128 | emitFilterValueChange = (value) => { 129 | const {onFilterValueChange} = this.props; 130 | if (onFilterValueChange) { 131 | onFilterValueChange(value); 132 | } 133 | } 134 | 135 | render() { 136 | const { 137 | filterHintText, 138 | toolbarIconRight, 139 | title, // eslint-disable-line no-unused-vars 140 | titleStyle, 141 | showFilterIcon, 142 | ...other, // eslint-disable-line no-unused-vars, comma-dangle 143 | } = this.props; 144 | 145 | const { 146 | mode, 147 | filterValue, 148 | } = this.state; 149 | 150 | const styles = getStyles(this.context); 151 | 152 | let contentNode; 153 | let filterIconNode; 154 | 155 | if (mode === 'default') { 156 | contentNode = (); 157 | } else if (mode === 'filter') { 158 | contentNode = ( 159 |
160 |
161 | 162 |
163 |
164 | { 171 | this.filterInput = textField ? textField.input : null; 172 | }} 173 | /> 174 |
175 |
176 | 180 | 181 | 182 |
183 |
184 | ); 185 | } 186 | 187 | const toolbarIconRightChildren = []; 188 | if (toolbarIconRight) { 189 | if (toolbarIconRight.length) { 190 | toolbarIconRight.map((toolbarIcon, i) => { 191 | toolbarIconRightChildren.push(React.cloneElement( 192 | toolbarIcon, 193 | { 194 | style: Object.assign(styles.headerToolbarIconButton, styles.icon), 195 | key: i, 196 | } 197 | )); 198 | }); 199 | } else { 200 | toolbarIconRightChildren.push(React.cloneElement( 201 | toolbarIconRight, 202 | { 203 | style: Object.assign(styles.headerToolbarIconButton, styles.icon), 204 | key: 1, 205 | } 206 | )); 207 | } 208 | } 209 | 210 | if (showFilterIcon) { 211 | filterIconNode = ( 212 | 216 | 219 | 220 | ); 221 | } 222 | 223 | return ( 224 | 225 | {contentNode} 226 | 227 | {filterIconNode} 228 | {toolbarIconRightChildren} 229 | 230 | 231 | ); 232 | } 233 | } 234 | 235 | export default DataTablesHeaderToolbar; 236 | -------------------------------------------------------------------------------- /src/DataTables/DataTablesRow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {TableRow} from 'material-ui/Table'; 4 | 5 | function getStyles(props, context, state) { 6 | const {tableRow} = context.muiTheme; 7 | 8 | let cellBgColor = 'inherit'; 9 | if (props.hovered || state.hovered) { 10 | cellBgColor = tableRow.hoverColor; 11 | } else if (props.selected) { 12 | cellBgColor = tableRow.selectedColor; 13 | } else if (props.striped) { 14 | cellBgColor = tableRow.stripeColor; 15 | } 16 | 17 | return { 18 | root: { 19 | borderBottom: props.displayBorder && `1px solid ${tableRow.borderColor}`, 20 | color: tableRow.textColor, 21 | height: tableRow.height, 22 | }, 23 | cell: { 24 | backgroundColor: cellBgColor, 25 | }, 26 | }; 27 | } 28 | 29 | class DataTablesTableRow extends TableRow { 30 | static propTypes = { 31 | /** 32 | * Children passed to table row. 33 | */ 34 | children: PropTypes.node, 35 | /** 36 | * The css class name of the root element. 37 | */ 38 | className: PropTypes.string, 39 | /** 40 | * If true, row border will be displayed for the row. 41 | * If false, no border will be drawn. 42 | */ 43 | displayBorder: PropTypes.bool, 44 | /** 45 | * Controls whether or not the row reponseds to hover events. 46 | */ 47 | hoverable: PropTypes.bool, 48 | /** 49 | * Controls whether or not the row should be rendered as being 50 | * hovered. This property is evaluated in addition to this.state.hovered 51 | * and can be used to synchronize the hovered state with some other 52 | * external events. 53 | */ 54 | hovered: PropTypes.bool, 55 | /** 56 | * @ignore 57 | * Called when a row cell is clicked. 58 | * rowNumber is the row number and columnId is 59 | * the column number or the column key. 60 | */ 61 | onCellClick: PropTypes.func, 62 | /** 63 | * @ignore 64 | * Customized handler 65 | * Called when a row cell is double clicked. 66 | */ 67 | onCellDoubleClick: PropTypes.func, 68 | /** 69 | * @ignore 70 | * Called when a table cell is hovered. 71 | * rowNumber is the row number of the hovered row 72 | * and columnId is the column number or the column key of the cell. 73 | */ 74 | onCellHover: PropTypes.func, 75 | /** 76 | * @ignore 77 | * Called when a table cell is no longer hovered. 78 | * rowNumber is the row number of the row and columnId 79 | * is the column number or the column key of the cell. 80 | */ 81 | onCellHoverExit: PropTypes.func, 82 | /** 83 | * @ignore 84 | * Called when row is clicked. 85 | */ 86 | onRowClick: PropTypes.func, 87 | /** 88 | * @ignore 89 | * Called when a table row is hovered. 90 | * rowNumber is the row number of the hovered row. 91 | */ 92 | onRowHover: PropTypes.func, 93 | /** 94 | * @ignore 95 | * Called when a table row is no longer hovered. 96 | * rowNumber is the row number of the row that is no longer hovered. 97 | */ 98 | onRowHoverExit: PropTypes.func, 99 | /** 100 | * Number to identify the row. This property is 101 | * automatically populated when used with the TableBody component. 102 | */ 103 | rowNumber: PropTypes.number, 104 | /** 105 | * If true, table rows can be selected. If multiple row 106 | * selection is desired, enable multiSelectable. 107 | * The default value is true. 108 | */ 109 | selectable: PropTypes.bool, 110 | /** 111 | * Indicates that a particular row is selected. 112 | * This property can be used to programmatically select rows. 113 | */ 114 | selected: PropTypes.bool, 115 | /** 116 | * Indicates whether or not the row is striped. 117 | */ 118 | striped: PropTypes.bool, 119 | /** 120 | * Override the inline-styles of the root element. 121 | */ 122 | style: PropTypes.object, 123 | }; 124 | 125 | clicked = false; 126 | 127 | clickTimer = undefined; 128 | 129 | onCellClick = (event, columnIndex) => { 130 | event.persist(); 131 | if (this.clicked) { 132 | this.clicked = false; 133 | clearTimeout(this.clickTimer); 134 | if (this.props.onCellDoubleClick) { 135 | this.props.onCellDoubleClick(event, this.props.rowNumber, columnIndex); 136 | } 137 | } else { 138 | this.clicked = true; 139 | this.clickTimer = setTimeout(() => { 140 | this.clicked = false; 141 | if (this.props.selectable && this.props.onCellClick) { 142 | this.props.onCellClick(event, this.props.rowNumber, columnIndex); 143 | } 144 | try { 145 | event.ctrlKey = true; 146 | } catch(e) { 147 | } 148 | this.onRowClick(event); 149 | }, 300); 150 | } 151 | }; 152 | 153 | render() { 154 | const { 155 | className, 156 | displayBorder, // eslint-disable-line no-unused-vars 157 | hoverable, // eslint-disable-line no-unused-vars 158 | hovered, // eslint-disable-line no-unused-vars 159 | onCellClick, // eslint-disable-line no-unused-vars 160 | onCellDoubleClick, // eslint-disable-line no-unused-vars 161 | onCellHover, // eslint-disable-line no-unused-vars 162 | onCellHoverExit, // eslint-disable-line no-unused-vars 163 | onRowClick, // eslint-disable-line no-unused-vars 164 | onRowHover, // eslint-disable-line no-unused-vars 165 | onRowHoverExit, // eslint-disable-line no-unused-vars 166 | rowNumber, // eslint-disable-line no-unused-vars 167 | selectable, // eslint-disable-line no-unused-vars 168 | selected, // eslint-disable-line no-unused-vars 169 | striped, // eslint-disable-line no-unused-vars 170 | style, 171 | ...other, // eslint-disable-line comma-dangle 172 | } = this.props; 173 | 174 | const {prepareStyles} = this.context.muiTheme; 175 | const styles = getStyles(this.props, this.context, this.state); 176 | 177 | const rowColumns = React.Children.map(this.props.children, (child, columnNumber) => { 178 | if (React.isValidElement(child)) { 179 | return React.cloneElement(child, { 180 | columnNumber: columnNumber, 181 | hoverable: this.props.hoverable, 182 | key: `${this.props.rowNumber}-${columnNumber}`, 183 | onClick: this.onCellClick, 184 | onHover: this.onCellHover, 185 | onHoverExit: this.onCellHoverExit, 186 | style: Object.assign({}, styles.cell, child.props.style), 187 | }); 188 | } 189 | }); 190 | 191 | return ( 192 | 197 | {rowColumns} 198 | 199 | ); 200 | } 201 | } 202 | 203 | export default DataTablesTableRow; 204 | -------------------------------------------------------------------------------- /src/DataTables/DataTablesRowColumn.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {TableRowColumn} from 'material-ui/Table'; 4 | 5 | function getStyles(props, context) { 6 | const {tableRowColumn} = context.muiTheme; 7 | 8 | const styles = { 9 | root: { 10 | paddingLeft: tableRowColumn.spacing, 11 | paddingRight: tableRowColumn.spacing, 12 | height: tableRowColumn.height, 13 | textAlign: props.alignRight ? 'right' : 'left', 14 | fontSize: 13, 15 | whiteSpace: 'nowrap', 16 | textOverflow: 'ellipsis', 17 | }, 18 | }; 19 | 20 | if (React.Children.count(props.children) === 1 && !isNaN(props.children)) { 21 | styles.textAlign = 'right'; 22 | } 23 | 24 | return styles; 25 | } 26 | 27 | class DataTablesRowColumn extends TableRowColumn { 28 | static muiName = 'TableRowColumn'; 29 | 30 | static propTypes = { 31 | children: PropTypes.node, 32 | /** 33 | * The css class name of the root element. 34 | */ 35 | className: PropTypes.string, 36 | /** 37 | * @ignore 38 | * Number to identify the header row. This property 39 | * is automatically populated when used with TableHeader. 40 | */ 41 | columnNumber: PropTypes.number, 42 | /** 43 | * @ignore 44 | * If true, this column responds to hover events. 45 | */ 46 | hoverable: PropTypes.bool, 47 | /** @ignore */ 48 | onClick: PropTypes.func, 49 | /** @ignore */ 50 | onDoubleClick: PropTypes.func, 51 | /** @ignore */ 52 | onHover: PropTypes.func, 53 | /** 54 | * @ignore 55 | * Callback function for hover exit event. 56 | */ 57 | onHoverExit: PropTypes.func, 58 | /** 59 | * Override the inline-styles of the root element. 60 | */ 61 | style: PropTypes.object, 62 | }; 63 | 64 | onDoubleClick = (event) => { 65 | if (this.props.onDoubleClick) { 66 | this.props.onDoubleClick(event, this.props.columnNumber); 67 | } 68 | }; 69 | 70 | render() { 71 | const { 72 | children, 73 | className, 74 | columnNumber, // eslint-disable-line no-unused-vars 75 | hoverable, // eslint-disable-line no-unused-vars 76 | onClick, // eslint-disable-line no-unused-vars 77 | onDoubleClick, // eslint-disable-line no-unused-vars 78 | onHover, // eslint-disable-line no-unused-vars 79 | onHoverExit, // eslint-disable-line no-unused-vars 80 | style, 81 | alignRight, // eslint-disable-line no-unused-vars 82 | ...other, // eslint-disable-line comma-dangle 83 | } = this.props; 84 | 85 | const {prepareStyles} = this.context.muiTheme; 86 | const styles = getStyles(this.props, this.context); 87 | 88 | const handlers = { 89 | onClick: this.onClick, 90 | onMouseEnter: this.onMouseEnter, 91 | onMouseLeave: this.onMouseLeave, 92 | onDoubleClick: this.onDoubleClick, 93 | }; 94 | 95 | return ( 96 | 102 | {children} 103 | 104 | ); 105 | } 106 | } 107 | 108 | export default DataTablesRowColumn; 109 | -------------------------------------------------------------------------------- /src/DataTables/DataTablesTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Table} from 'material-ui/Table'; 3 | 4 | class DataTablesTable extends Table { 5 | 6 | createTableBody(base) { 7 | return React.cloneElement( 8 | base, 9 | { 10 | allRowsSelected: this.state.allRowsSelected, 11 | multiSelectable: this.props.multiSelectable, 12 | onCellClick: this.onCellClick, 13 | onCellDoubleClick: this.onCellDoubleClick, 14 | onCellHover: this.onCellHover, 15 | onCellHoverExit: this.onCellHoverExit, 16 | onRowHover: this.onRowHover, 17 | onRowHoverExit: this.onRowHoverExit, 18 | onRowSelection: this.onRowSelection, 19 | selectable: this.props.selectable, 20 | style: Object.assign({height: this.props.height}, base.props.style), 21 | } 22 | ); 23 | } 24 | 25 | onCellDoubleClick = (rowNumber, columnNumber, event) => { 26 | if (this.props.onCellDoubleClick) this.props.onCellDoubleClick(rowNumber, columnNumber, event); 27 | }; 28 | } 29 | 30 | export default DataTablesTable; 31 | -------------------------------------------------------------------------------- /src/DataTables/DataTablesTableBody.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {TableBody} from 'material-ui/Table'; 4 | import ClickAwayListener from 'material-ui/internal/ClickAwayListener'; 5 | 6 | class DataTablesTableBody extends TableBody { 7 | static muiName = 'TableBody'; 8 | 9 | static propTypes = { 10 | /** 11 | * @ignore 12 | * Set to true to indicate that all rows should be selected. 13 | */ 14 | allRowsSelected: PropTypes.bool, 15 | /** 16 | * Children passed to table body. 17 | */ 18 | children: PropTypes.node, 19 | /** 20 | * The css class name of the root element. 21 | */ 22 | className: PropTypes.string, 23 | /** 24 | * Controls whether or not to deselect all selected 25 | * rows after clicking outside the table. 26 | */ 27 | deselectOnClickaway: PropTypes.bool, 28 | /** 29 | * Controls the display of the row checkbox. The default value is true. 30 | */ 31 | displayRowCheckbox: PropTypes.bool, 32 | /** 33 | * @ignore 34 | * If true, multiple table rows can be selected. 35 | * CTRL/CMD+Click and SHIFT+Click are valid actions. 36 | * The default value is false. 37 | */ 38 | multiSelectable: PropTypes.bool, 39 | /** 40 | * @ignore 41 | * Callback function for when a cell is clicked. 42 | */ 43 | onCellClick: PropTypes.func, 44 | /** 45 | * @ignore 46 | * Customized handler 47 | * Callback function for when a cell is double clicked. 48 | */ 49 | onCellDoubleClick: PropTypes.func, 50 | /** 51 | * @ignore 52 | * Called when a table cell is hovered. rowNumber 53 | * is the row number of the hovered row and columnId 54 | * is the column number or the column key of the cell. 55 | */ 56 | onCellHover: PropTypes.func, 57 | /** 58 | * @ignore 59 | * Called when a table cell is no longer hovered. 60 | * rowNumber is the row number of the row and columnId 61 | * is the column number or the column key of the cell. 62 | */ 63 | onCellHoverExit: PropTypes.func, 64 | /** 65 | * @ignore 66 | * Called when a table row is hovered. 67 | * rowNumber is the row number of the hovered row. 68 | */ 69 | onRowHover: PropTypes.func, 70 | /** 71 | * @ignore 72 | * Called when a table row is no longer 73 | * hovered. rowNumber is the row number of the row 74 | * that is no longer hovered. 75 | */ 76 | onRowHoverExit: PropTypes.func, 77 | /** 78 | * @ignore 79 | * Called when a row is selected. selectedRows is an 80 | * array of all row selections. IF all rows have been selected, 81 | * the string "all" will be returned instead to indicate that 82 | * all rows have been selected. 83 | */ 84 | onRowSelection: PropTypes.func, 85 | /** 86 | * Controls whether or not the rows are pre-scanned to determine 87 | * initial state. If your table has a large number of rows and 88 | * you are experiencing a delay in rendering, turn off this property. 89 | */ 90 | preScanRows: PropTypes.bool, 91 | /** 92 | * @ignore 93 | * If true, table rows can be selected. If multiple 94 | * row selection is desired, enable multiSelectable. 95 | * The default value is true. 96 | */ 97 | selectable: PropTypes.bool, 98 | /** 99 | * If true, table rows will be highlighted when 100 | * the cursor is hovering over the row. The default 101 | * value is false. 102 | */ 103 | showRowHover: PropTypes.bool, 104 | /** 105 | * If true, every other table row starting 106 | * with the first row will be striped. The default value is false. 107 | */ 108 | stripedRows: PropTypes.bool, 109 | /** 110 | * Override the inline-styles of the root element. 111 | */ 112 | style: PropTypes.object, 113 | }; 114 | 115 | createRows() { 116 | const numChildren = React.Children.count(this.props.children); 117 | let rowNumber = 0; 118 | const handlers = { 119 | onCellClick: this.onCellClick, 120 | onCellDoubleClick: this.onCellDoubleClick, 121 | onCellHover: this.onCellHover, 122 | onCellHoverExit: this.onCellHoverExit, 123 | onRowHover: this.onRowHover, 124 | onRowHoverExit: this.onRowHoverExit, 125 | onRowClick: this.onRowClick, 126 | }; 127 | 128 | return React.Children.map(this.props.children, (child) => { 129 | if (React.isValidElement(child)) { 130 | const props = { 131 | hoverable: this.props.showRowHover, 132 | selected: this.isRowSelected(rowNumber), 133 | striped: this.props.stripedRows && (rowNumber % 2 === 0), 134 | rowNumber: rowNumber++, 135 | }; 136 | 137 | if (rowNumber === numChildren) { 138 | props.displayBorder = false; 139 | } 140 | 141 | const children = [ 142 | this.createRowCheckboxColumn(props), 143 | ]; 144 | 145 | React.Children.forEach(child.props.children, (child) => { 146 | children.push(child); 147 | }); 148 | 149 | return React.cloneElement(child, {...props, ...handlers}, children); 150 | } 151 | }); 152 | } 153 | 154 | onCellDoubleClick = (event, rowNumber, columnNumber) => { 155 | event.stopPropagation(); 156 | if (this.props.onCellDoubleClick) { 157 | this.props.onCellDoubleClick(rowNumber, this.getColumnId(columnNumber), event); 158 | } 159 | }; 160 | 161 | render() { 162 | const { 163 | style, 164 | allRowsSelected, // eslint-disable-line no-unused-vars 165 | multiSelectable, // eslint-disable-line no-unused-vars 166 | onCellClick, // eslint-disable-line no-unused-vars 167 | onCellDoubleClick, // eslint-disable-line no-unused-vars 168 | onCellHover, // eslint-disable-line no-unused-vars 169 | onCellHoverExit, // eslint-disable-line no-unused-vars 170 | onRowHover, // eslint-disable-line no-unused-vars 171 | onRowHoverExit, // eslint-disable-line no-unused-vars 172 | onRowSelection, // eslint-disable-line no-unused-vars 173 | selectable, // eslint-disable-line no-unused-vars 174 | deselectOnClickaway, // eslint-disable-line no-unused-vars 175 | showRowHover, // eslint-disable-line no-unused-vars 176 | stripedRows, // eslint-disable-line no-unused-vars 177 | displayRowCheckbox, // eslint-disable-line no-unused-vars 178 | preScanRows, // eslint-disable-line no-unused-vars 179 | ...other 180 | } = this.props; 181 | 182 | const {prepareStyles} = this.context.muiTheme; 183 | 184 | return ( 185 | 186 | 187 | {this.createRows()} 188 | 189 | 190 | ); 191 | } 192 | } 193 | 194 | export default DataTablesTableBody; 195 | -------------------------------------------------------------------------------- /src/DataTables/index.js: -------------------------------------------------------------------------------- 1 | export DataTables from './DataTables'; 2 | 3 | export default from './DataTables'; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export DataTables from './DataTables'; 2 | 3 | export default from './DataTables'; 4 | -------------------------------------------------------------------------------- /test/DataTables/DataTables.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import {shallow, mount} from 'enzyme'; 4 | import {expect} from 'chai'; 5 | import sinon from 'sinon'; 6 | 7 | import getMuiTheme from 'material-ui/styles/getMuiTheme'; 8 | import {TableHeader} from 'material-ui/Table'; 9 | import {Toolbar, ToolbarTitle} from 'material-ui/Toolbar'; 10 | import DropDownMenu from 'material-ui/DropDownMenu'; 11 | import MenuItem from 'material-ui/MenuItem'; 12 | import FlatButton from 'material-ui/FlatButton'; 13 | import IconButton from 'material-ui/IconButton'; 14 | import TextField from 'material-ui/TextField'; 15 | import PersonAdd from 'material-ui/svg-icons/social/person-add'; 16 | import InfoOutline from 'material-ui/svg-icons/action/info-outline'; 17 | import FilterListIcon from 'material-ui/svg-icons/content/filter-list'; 18 | import {deepOrange500} from 'material-ui/styles/colors'; 19 | 20 | import { 21 | TABLE_COLUMNS, 22 | TABLE_COLUMNS_TOOLTIP, 23 | TABLE_COLUMNS_SORT_STYLE, 24 | TABLE_COLUMNS_CLASSNAME, 25 | TABLE_DATA, 26 | styles, 27 | } from './tableSettings'; 28 | import DataTables from '../../src/DataTables/DataTables'; 29 | import DataTablesHeaderColumn from '../../src/DataTables/DataTablesHeaderColumn'; 30 | import DataTablesRow from '../../src/DataTables/DataTablesRow'; 31 | import DataTablesRowColumn from '../../src/DataTables/DataTablesRowColumn'; 32 | import DataTablesHeaderToolbar from '../../src/DataTables/DataTablesHeaderToolbar'; 33 | import DataTablesTable from '../../src/DataTables/DataTablesTable'; 34 | 35 | describe('', function() { 36 | describe('Basic', function() { 37 | let wrapper; 38 | const muiTheme = getMuiTheme(); 39 | 40 | before(function() { 41 | // shallow rendering 42 | wrapper = shallow( 43 | , 52 | { 53 | context: {muiTheme: muiTheme}, 54 | } 55 | ); 56 | }); 57 | 58 | it('should render table header', function() { 59 | expect(wrapper.find(TableHeader)).to.have.length(1); 60 | }); 61 | it('should render header columns', function() { 62 | const headerColumns = wrapper.find(DataTablesHeaderColumn); 63 | expect(headerColumns).to.have.length(8); 64 | expect(headerColumns.getNodes()[0].props.children.props.children).to.equal('Dessert (100g serving)'); 65 | expect(headerColumns.getNodes()[1].props.children.props.children).to.equal('Calories'); 66 | expect(headerColumns.getNodes()[2].props.children.props.children).to.equal('Fat (g)'); 67 | expect(headerColumns.getNodes()[3].props.children.props.children).to.equal('Carbs (g)'); 68 | expect(headerColumns.getNodes()[4].props.children.props.children).to.equal('Protein (g)'); 69 | expect(headerColumns.getNodes()[5].props.children.props.children).to.equal('Sodium (mg)'); 70 | expect(headerColumns.getNodes()[6].props.children.props.children).to.equal('Calcium (%)'); 71 | expect(headerColumns.getNodes()[7].props.children.props.children).to.equal('Iron (%)'); 72 | }); 73 | it('should render 10 data rows by default', function() { 74 | const dataRows = wrapper.find(DataTablesRow); 75 | expect(dataRows).to.have.length(10); 76 | }); 77 | it('should render data columns', function() { 78 | const dataRows = wrapper.find(DataTablesRow); 79 | expect(dataRows.getNodes()[3].props.children[0].props.children).to.equal('Cupcake'); 80 | expect(dataRows.getNodes()[3].props.children[1].props.children).to.equal('159'); 81 | expect(dataRows.getNodes()[3].props.children[2].props.children).to.equal('6.0'); 82 | expect(dataRows.getNodes()[3].props.children[3].props.children).to.equal('24'); 83 | expect(dataRows.getNodes()[3].props.children[4].props.children).to.equal('4.0'); 84 | expect(dataRows.getNodes()[3].props.children[5].props.children).to.equal('87'); 85 | expect(dataRows.getNodes()[3].props.children[6].props.children).to.equal('14%'); 86 | expect(dataRows.getNodes()[3].props.children[7].props.children).to.equal('1%'); 87 | }); 88 | it('should render data columns render function', function() { 89 | const dataRows = wrapper.find(DataTablesRow); 90 | expect(dataRows.getNodes()[2].props.children[2].props.children).to.equal('6.0'); 91 | }); 92 | it('should render row size label', function() { 93 | const rowSizeLabelWrapper = wrapper.find({style: styles.footerToolbarItem}).getNodes()[0]; 94 | expect(rowSizeLabelWrapper.props.children.props.children).to.equal('Rows per page:'); 95 | }); 96 | it('should render row size menu', function() { 97 | expect(wrapper.find(DropDownMenu)).to.have.length(1); 98 | }); 99 | it('should render 10, 30, 50, 100 row size items by default', function() { 100 | const rowSizeItems = wrapper.find(MenuItem); 101 | expect(rowSizeItems).to.have.length(4); 102 | expect(rowSizeItems.getNodes()[0].props.value).to.equal(10); 103 | expect(rowSizeItems.getNodes()[1].props.value).to.equal(30); 104 | expect(rowSizeItems.getNodes()[2].props.value).to.equal(50); 105 | expect(rowSizeItems.getNodes()[3].props.value).to.equal(100); 106 | }); 107 | it('should render summary label', function() { 108 | const summaryLabelWrapper = wrapper.find({style: styles.footerToolbarItem}).getNodes()[1]; 109 | expect(summaryLabelWrapper.props.children.props.children).to.equal('1 - 10 of 100'); 110 | }); 111 | it('should render pagination buttons', function() { 112 | expect(wrapper.find(FlatButton)).to.have.length(2); 113 | }); 114 | it('should not render checkboxes', function() { 115 | expect(wrapper.find('[type="checkbox"]')).to.have.length(0); 116 | }); 117 | it('should render footer toolbar', function() { 118 | expect(wrapper.find({style: styles.footerToolbar})).to.have.length(1); 119 | }); 120 | }); 121 | 122 | describe('Selectable & Tooltip & Pagination', function() { 123 | const handleNextPageClick = sinon.spy(); 124 | const handlePreviousPageClick = sinon.spy(); 125 | const handleRowSelection = sinon.spy(); 126 | let wrapper; 127 | const muiTheme = getMuiTheme(); 128 | 129 | before(function() { 130 | // full rendering 131 | wrapper = mount( 132 | , 147 | { 148 | context: {muiTheme: muiTheme}, 149 | childContextTypes: {muiTheme: PropTypes.object}, 150 | } 151 | ); 152 | 153 | // dummy 154 | global.window.getSelection = function() { 155 | return { 156 | removeAllRanges: function() { 157 | }, 158 | }; 159 | }; 160 | }); 161 | 162 | it('should call row select handler', function(done) { 163 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 164 | setTimeout(() => { 165 | expect(handleRowSelection).to.have.property('callCount', 1); 166 | expect(wrapper.find(DataTablesRow).first().prop('selected')).to.equal(true); 167 | done(); 168 | }, 500); 169 | }); 170 | it('should render tooltips', function() { 171 | const headerColumns = wrapper.find(DataTablesHeaderColumn); 172 | expect(headerColumns.getNodes()[0].props.tooltip).to.equal('Dessert (100g serving)'); 173 | expect(headerColumns.getNodes()[1].props.tooltip).to.equal('Calories'); 174 | }); 175 | it('should call pagination handler', function() { 176 | wrapper.find(FlatButton).last().simulate('click'); 177 | expect(handleNextPageClick).to.have.property('callCount', 1); 178 | wrapper.setProps({page: 2}); 179 | wrapper.find(FlatButton).first().simulate('click'); 180 | expect(handlePreviousPageClick).to.have.property('callCount', 1); 181 | }); 182 | it('should render checkboxes', function() { 183 | expect(wrapper.find('[type="checkbox"]')).to.have.length(11); 184 | }); 185 | }); 186 | 187 | describe('Filter & Column Sort & Styled Column', function() { 188 | const handleCellClick = sinon.spy(); 189 | const handleCellDoubleClick = sinon.spy(); 190 | const handleFilterValueChange = sinon.spy(); 191 | const handleSortOrderChange = sinon.spy(); 192 | const handleRowSizeChange = sinon.spy(); 193 | let wrapper; 194 | const muiTheme = getMuiTheme(); 195 | 196 | before(function() { 197 | // full rendering 198 | wrapper = mount( 199 | , 215 | { 216 | context: {muiTheme: muiTheme}, 217 | childContextTypes: {muiTheme: PropTypes.object}, 218 | } 219 | ); 220 | 221 | // dummy 222 | global.window.getSelection = function() { 223 | return { 224 | removeAllRanges: function() { 225 | }, 226 | }; 227 | }; 228 | }); 229 | 230 | it('should render header tool bar', function() { 231 | expect(wrapper.find(DataTablesHeaderToolbar)).to.have.length(1); 232 | }); 233 | it('should render data tables title', function() { 234 | expect(wrapper.find(DataTablesHeaderToolbar).prop('title')).to.equal('Nutrition'); 235 | }); 236 | it('should call click handler', function(done) { 237 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 238 | setTimeout(() => { 239 | expect(handleCellClick).to.have.property('callCount', 1); 240 | done(); 241 | }, 500); 242 | }); 243 | it('should call dobule click handler', function() { 244 | // simulate double click 245 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 246 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 247 | expect(handleCellDoubleClick).to.have.property('callCount', 1); 248 | }); 249 | it('should call filter value change handler and set focus to input', function(done) { 250 | wrapper.find(DataTablesHeaderToolbar).find(IconButton).simulate('click'); 251 | const inputNodeInstanceId = Object.keys(wrapper.find(DataTablesHeaderToolbar).find(TextField).find('input').getDOMNode())[0]; 252 | const inputDomId = wrapper.find(DataTablesHeaderToolbar).find(TextField).find('input').getDOMNode()[inputNodeInstanceId]._domID; 253 | const activeElementDomId = document.activeElement[inputNodeInstanceId]._domID; 254 | expect(inputDomId).to.equal(activeElementDomId); 255 | wrapper.find(DataTablesHeaderToolbar).find(TextField).find('input') 256 | .simulate('change', {target: {value: 'dummy'}}); 257 | setTimeout(() => { 258 | expect(handleFilterValueChange).to.have.property('callCount', 1); 259 | expect(handleFilterValueChange.calledWith('dummy')).to.equal(true); 260 | done(); 261 | }, 800); 262 | }); 263 | it('should call row size change handler', function() { 264 | wrapper.find(DropDownMenu).props().onChange({}, 1, 30); 265 | expect(handleRowSizeChange).to.have.property('callCount', 1); 266 | expect(handleRowSizeChange.calledWith(1, 30)).to.equal(true); 267 | }); 268 | it('should call sort order change handler', function() { 269 | wrapper.find(DataTablesHeaderColumn).first().simulate('click'); 270 | expect(handleSortOrderChange).to.have.property('callCount', 1); 271 | expect(handleSortOrderChange.calledWith('name', 'asc')).to.equal(true); 272 | wrapper.find(DataTablesHeaderColumn).first().simulate('click'); 273 | expect(handleSortOrderChange.calledWith('name', 'desc')).to.equal(true); 274 | }); 275 | }); 276 | 277 | describe('Internationalization', function() { 278 | let wrapper; 279 | const muiTheme = getMuiTheme(); 280 | 281 | before(function() { 282 | // full rendering 283 | wrapper = mount( 284 | `${start} - ${end} ${count}件` 295 | } 296 | showCheckboxes={false} 297 | showHeaderToolbar={true} 298 | count={100} 299 | />, 300 | { 301 | context: {muiTheme: muiTheme}, 302 | childContextTypes: {muiTheme: PropTypes.object}, 303 | } 304 | ); 305 | }); 306 | 307 | it('should render data tables title', function() { 308 | expect(wrapper.find(DataTablesHeaderToolbar).prop('title')).to.equal('ニュートリション'); 309 | }); 310 | it('should render row size label with customized label', function() { 311 | expect( 312 | wrapper.find({style: styles.footerToolbarItem}) 313 | .at(0).props().children.props.children 314 | ) 315 | .to.equal('ページサイズ'); 316 | }); 317 | it('should render summary label with customized label', function() { 318 | expect( 319 | wrapper.find({style: styles.footerToolbarItem}) 320 | .at(1).props().children.props.children 321 | ) 322 | .to.equal('1 - 10 100件'); 323 | }); 324 | it('should render search hint with customized text', function() { 325 | wrapper.find(DataTablesHeaderToolbar).find(IconButton).simulate('click'); 326 | expect(wrapper.find(DataTablesHeaderToolbar).find(TextField).prop('hintText')).to.equal('検索'); 327 | }); 328 | }); 329 | 330 | describe('Toolbar Icons & Styled title & Styled table', function() { 331 | let wrapper; 332 | const muiTheme = getMuiTheme(); 333 | 334 | before(function() { 335 | // full rendering 336 | wrapper = mount( 337 | 354 | 355 | , 356 | 357 | 358 | , 359 | ]} 360 | />, 361 | { 362 | context: {muiTheme: muiTheme}, 363 | childContextTypes: {muiTheme: PropTypes.object}, 364 | } 365 | ); 366 | }); 367 | 368 | it('should render custom tool bar icons', function() { 369 | expect(wrapper.find(DataTablesHeaderToolbar).find(IconButton)).to.have.length(3); 370 | expect(wrapper.find(DataTablesHeaderToolbar).find(FilterListIcon)).to.have.length(1); 371 | expect(wrapper.find(DataTablesHeaderToolbar).find(PersonAdd)).to.have.length(1); 372 | expect(wrapper.find(DataTablesHeaderToolbar).find(InfoOutline)).to.have.length(1); 373 | }); 374 | 375 | it('should have inline styles for title', function() { 376 | expect(wrapper.find(ToolbarTitle).prop('style').fontSize).not.to.equal(undefined); 377 | expect(wrapper.find(ToolbarTitle).prop('style').color).not.to.equal(undefined); 378 | expect(wrapper.find(ToolbarTitle).prop('style').fontSize).to.equal(16); 379 | expect(wrapper.find(ToolbarTitle).prop('style').color).to.equal(deepOrange500); 380 | }); 381 | 382 | it('should have footerToolbar styles defined', function() { 383 | expect(wrapper.find(Toolbar).at(1).prop('style').padding).to.equal('0 100px'); 384 | }); 385 | 386 | it('should have table styles defined', function() { 387 | expect(wrapper.find(DataTablesTable).at(0).prop('style').tableLayout).to.equal('auto'); 388 | }); 389 | 390 | it('should have table body styles defined', function() { 391 | expect(wrapper.find(DataTablesTable).at(0).prop('bodyStyle').overflowX).to.equal('auto'); 392 | }); 393 | 394 | it('should have table wrapper styles defined', function() { 395 | expect(wrapper.find(DataTablesTable).at(0).prop('wrapperStyle').padding).to.equal(5); 396 | }); 397 | }); 398 | 399 | describe('Column class name', function() { 400 | let wrapper; 401 | const muiTheme = getMuiTheme(); 402 | 403 | before(function() { 404 | // full rendering 405 | wrapper = mount( 406 | , 418 | { 419 | context: {muiTheme: muiTheme}, 420 | childContextTypes: {muiTheme: PropTypes.object}, 421 | } 422 | ); 423 | }); 424 | 425 | it('should have column class name', function() { 426 | const headerColumns = wrapper.find(DataTablesHeaderColumn); 427 | expect(headerColumns.getNodes()[0].props.className).to.equal('important-column'); 428 | expect(headerColumns.getNodes()[1].props.className).to.equal('important-column'); 429 | expect(headerColumns.getNodes()[2].props.className).to.equal(undefined); 430 | }); 431 | }); 432 | 433 | describe('Programmatically select rows', function() { 434 | let wrapper; 435 | const muiTheme = getMuiTheme(); 436 | 437 | before(function() { 438 | // full rendering 439 | wrapper = mount( 440 | , 453 | { 454 | context: {muiTheme: muiTheme}, 455 | childContextTypes: {muiTheme: PropTypes.object}, 456 | } 457 | ); 458 | }); 459 | 460 | it('should render selected rows by default', function() { 461 | const tableRows = wrapper.find(DataTablesRow); 462 | expect(tableRows.getNodes()[0].props.selected).to.equal(true); 463 | expect(tableRows.getNodes()[1].props.selected).to.equal(false); 464 | expect(tableRows.getNodes()[2].props.selected).to.equal(true); 465 | expect(tableRows.getNodes()[3].props.selected).to.equal(false); 466 | expect(tableRows.getNodes()[4].props.selected).to.equal(false); 467 | expect(tableRows.getNodes()[5].props.selected).to.equal(true); 468 | }); 469 | }); 470 | 471 | describe('onRowSelection handler & onCellDoubleClick handler', function() { 472 | const handleRowSelection = sinon.spy(); 473 | const handleCellDoubleClick = sinon.spy(); 474 | let wrapper; 475 | const muiTheme = getMuiTheme(); 476 | 477 | before(function() { 478 | // full rendering 479 | wrapper = mount( 480 | , 494 | { 495 | context: {muiTheme: muiTheme}, 496 | childContextTypes: {muiTheme: PropTypes.object}, 497 | } 498 | ); 499 | 500 | // dummy 501 | global.window.getSelection = function() { 502 | return { 503 | removeAllRanges: function() { 504 | }, 505 | }; 506 | }; 507 | }); 508 | 509 | it('should call row select handler', function(done) { 510 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 511 | setTimeout(() => { 512 | expect(handleRowSelection).to.have.property('callCount', 1); 513 | done(); 514 | }, 500); 515 | }); 516 | 517 | it('should call dobule click handler', function() { 518 | // simulate double click 519 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 520 | wrapper.find(DataTablesRowColumn).first().simulate('click'); 521 | expect(handleCellDoubleClick).to.have.property('callCount', 1); 522 | }); 523 | }); 524 | 525 | describe('row size controls', function() { 526 | const muiTheme = getMuiTheme(); 527 | describe('when showRowSizeControls = false', function() { 528 | let wrapper; 529 | before(function() { 530 | wrapper = mount( 531 | , 544 | { 545 | context: {muiTheme: muiTheme}, 546 | childContextTypes: {muiTheme: PropTypes.object}, 547 | } 548 | ); 549 | }); 550 | 551 | it('should not render row size label', function() { 552 | expect(wrapper.find({style: styles.footerToolbarItem})).to.have.length(2); 553 | }); 554 | 555 | it('should not render row size menu', function() { 556 | expect(wrapper.find(DropDownMenu)).to.have.length(0); 557 | }); 558 | 559 | it('should not render any row size items', function() { 560 | const rowSizeItems = wrapper.find(MenuItem); 561 | expect(rowSizeItems).to.have.length(0); 562 | }); 563 | }); 564 | 565 | describe( 'when rowSizeList is empty', function() { 566 | let wrapper; 567 | before(function() { 568 | wrapper = mount( 569 | , 583 | { 584 | context: {muiTheme: muiTheme}, 585 | childContextTypes: {muiTheme: PropTypes.object}, 586 | } 587 | ); 588 | }); 589 | 590 | it('should render row size label', function() { 591 | expect(wrapper.find({style: styles.footerToolbarItem})).to.have.length(3); 592 | }); 593 | 594 | it('should not render row size menu', function() { 595 | expect(wrapper.find(DropDownMenu)).to.have.length(0); 596 | }); 597 | }); 598 | }); 599 | 600 | describe('Disable footer toolbar', function() { 601 | let wrapper; 602 | const muiTheme = getMuiTheme(); 603 | 604 | before(function() { 605 | // full rendering 606 | wrapper = mount( 607 | , 619 | { 620 | context: {muiTheme: muiTheme}, 621 | childContextTypes: {muiTheme: PropTypes.object}, 622 | } 623 | ); 624 | }); 625 | 626 | it('should not render footer toolbar', function() { 627 | expect(wrapper.find({style: styles.footerToolbar})).to.have.length(0); 628 | }); 629 | }); 630 | 631 | describe('Header toolbar mode', function() { 632 | let wrapper; 633 | const muiTheme = getMuiTheme(); 634 | 635 | before(function() { 636 | // full rendering 637 | wrapper = mount( 638 | , 652 | { 653 | context: {muiTheme: muiTheme}, 654 | childContextTypes: {muiTheme: PropTypes.object}, 655 | } 656 | ); 657 | }); 658 | 659 | it('should render header toolbar with filter mode', function() { 660 | expect(wrapper.find(DataTablesHeaderToolbar).prop('mode')).to.equal('filter'); 661 | expect(wrapper.find(DataTablesHeaderToolbar).prop('filterValue')).to.equal('test'); 662 | }); 663 | }); 664 | 665 | describe('Hide filter icon', function() { 666 | let wrapper; 667 | const muiTheme = getMuiTheme(); 668 | 669 | before(function() { 670 | // full rendering 671 | wrapper = mount( 672 | , 685 | { 686 | context: {muiTheme: muiTheme}, 687 | childContextTypes: {muiTheme: PropTypes.object}, 688 | } 689 | ); 690 | }); 691 | 692 | it('should not render filter icon in header toolbar', function() { 693 | expect(wrapper.find(DataTablesHeaderToolbar).find(FilterListIcon)).to.have.length(0); 694 | }); 695 | }); 696 | }); 697 | -------------------------------------------------------------------------------- /test/DataTables/tableSettings.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {deepOrange500} from 'material-ui/styles/colors'; 3 | 4 | export const TABLE_COLUMNS = [ 5 | { 6 | key: 'name', 7 | label: 'Dessert (100g serving)', 8 | }, { 9 | key: 'calories', 10 | label: 'Calories', 11 | }, { 12 | key: 'fat', 13 | label: 'Fat (g)', 14 | }, { 15 | key: 'carbs', 16 | label: 'Carbs (g)', 17 | }, { 18 | key: 'protein', 19 | label: 'Protein (g)', 20 | }, { 21 | key: 'sodium', 22 | label: 'Sodium (mg)', 23 | }, { 24 | key: 'calcium', 25 | label: 'Calcium (%)', 26 | }, { 27 | key: 'iron', 28 | label: 'Iron (%)', 29 | }, 30 | ]; 31 | 32 | export const TABLE_COLUMNS_TOOLTIP = [ 33 | { 34 | key: 'name', 35 | label: 'Dessert (100g serving)', 36 | tooltip: 'Dessert (100g serving)', 37 | }, { 38 | key: 'calories', 39 | label: 'Calories', 40 | tooltip: 'Calories', 41 | }, { 42 | key: 'fat', 43 | label: 'Fat (g)', 44 | tooltip: 'Fat (g)', 45 | }, { 46 | key: 'carbs', 47 | label: 'Carbs (g)', 48 | tooltip: 'Carbs (g)', 49 | }, { 50 | key: 'protein', 51 | label: 'Protein (g)', 52 | tooltip: 'Protein (g)', 53 | }, { 54 | key: 'sodium', 55 | label: 'Sodium (mg)', 56 | tooltip: 'Sodium (mg)', 57 | }, { 58 | key: 'calcium', 59 | label: 'Calcium (%)', 60 | tooltip: 'Calcium (%)', 61 | }, { 62 | key: 'iron', 63 | label: 'Iron (%)', 64 | tooltip: 'Iron (%)', 65 | }, 66 | ]; 67 | 68 | export const TABLE_COLUMNS_SORT_STYLE = [ 69 | { 70 | key: 'name', 71 | label: 'Dessert (100g serving)', 72 | sortable: true, 73 | style: { 74 | width: 250, 75 | }, 76 | }, { 77 | key: 'calories', 78 | label: 'Calories', 79 | sortable: true, 80 | }, { 81 | key: 'fat', 82 | label: 'Fat (g)', 83 | render: (fat) =>
{fat}
84 | }, { 85 | key: 'carbs', 86 | label: 'Carbs (g)', 87 | }, { 88 | key: 'protein', 89 | label: 'Protein (g)', 90 | }, { 91 | key: 'sodium', 92 | label: 'Sodium (mg)', 93 | }, { 94 | key: 'calcium', 95 | label: 'Calcium (%)', 96 | }, { 97 | key: 'iron', 98 | label: 'Iron (%)', 99 | }, 100 | ]; 101 | 102 | export const TABLE_COLUMNS_CLASSNAME = [ 103 | { 104 | key: 'name', 105 | label: 'Dessert (100g serving)', 106 | className: 'important-column', 107 | }, { 108 | key: 'calories', 109 | label: 'Calories', 110 | className: 'important-column', 111 | }, { 112 | key: 'fat', 113 | label: 'Fat (g)', 114 | }, { 115 | key: 'carbs', 116 | label: 'Carbs (g)', 117 | }, { 118 | key: 'protein', 119 | label: 'Protein (g)', 120 | }, { 121 | key: 'sodium', 122 | label: 'Sodium (mg)', 123 | }, { 124 | key: 'calcium', 125 | label: 'Calcium (%)', 126 | }, { 127 | key: 'iron', 128 | label: 'Iron (%)', 129 | }, 130 | ]; 131 | 132 | export const TABLE_DATA = [ 133 | { 134 | name: 'Frozen yogurt', 135 | calories: '159', 136 | fat: '6.0', 137 | carbs: '24', 138 | protein: '4.0', 139 | sodium: '87', 140 | calcium: '14%', 141 | iron: '1%', 142 | }, { 143 | name: 'Ice cream sandwich', 144 | calories: '159', 145 | fat: '6.0', 146 | carbs: '24', 147 | protein: '4.0', 148 | sodium: '87', 149 | calcium: '14%', 150 | iron: '1%', 151 | }, { 152 | name: 'Eclair', 153 | calories: '159', 154 | fat: '6.0', 155 | carbs: '24', 156 | protein: '4.0', 157 | sodium: '87', 158 | calcium: '14%', 159 | iron: '1%', 160 | }, { 161 | name: 'Cupcake', 162 | calories: '159', 163 | fat: '6.0', 164 | carbs: '24', 165 | protein: '4.0', 166 | sodium: '87', 167 | calcium: '14%', 168 | iron: '1%', 169 | }, { 170 | name: 'Gingerbread', 171 | calories: '159', 172 | fat: '6.0', 173 | carbs: '24', 174 | protein: '4.0', 175 | sodium: '87', 176 | calcium: '14%', 177 | iron: '1%', 178 | }, { 179 | name: 'Jelly bean', 180 | calories: '159', 181 | fat: '6.0', 182 | carbs: '24', 183 | protein: '4.0', 184 | sodium: '87', 185 | calcium: '14%', 186 | iron: '1%', 187 | }, { 188 | name: 'Lollipop', 189 | calories: '159', 190 | fat: '6.0', 191 | carbs: '24', 192 | protein: '4.0', 193 | sodium: '87', 194 | calcium: '14%', 195 | iron: '1%', 196 | }, { 197 | name: 'Honeycomb', 198 | calories: '159', 199 | fat: '6.0', 200 | carbs: '24', 201 | protein: '4.0', 202 | sodium: '87', 203 | calcium: '14%', 204 | iron: '1%', 205 | }, { 206 | name: 'Donut', 207 | calories: '159', 208 | fat: '6.0', 209 | carbs: '24', 210 | protein: '4.0', 211 | sodium: '87', 212 | calcium: '14%', 213 | iron: '1%', 214 | }, { 215 | name: 'KitKat', 216 | calories: '159', 217 | fat: '6.0', 218 | carbs: '24', 219 | protein: '4.0', 220 | sodium: '87', 221 | calcium: '14%', 222 | iron: '1%', 223 | }, 224 | ]; 225 | 226 | export const styles = { 227 | footerToolbarItem: { 228 | marginLeft: 8, 229 | marginRight: 8, 230 | alignItems: 'center', 231 | display: 'flex', 232 | }, 233 | titleStyle: { 234 | fontSize: 16, 235 | color: deepOrange500, 236 | }, 237 | footerToolbarStyle: { 238 | padding: '0 100px', 239 | }, 240 | tableStyle: { 241 | tableLayout: 'auto', 242 | }, 243 | tableBodyStyle: { 244 | overflowX: 'auto', 245 | }, 246 | tableWrapperStyle: { 247 | padding: 5, 248 | }, 249 | }; 250 | --------------------------------------------------------------------------------