diff --git a/README.md b/README.md index 31af8af27..7dbf8764d 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,8 @@ The component accepts the following props: |**`pagination`**|boolean|true|Enable/disable pagination |**`selectableRows`**|boolean|true|Enable/disable row selection |**`resizableColumns`**|boolean|false|Enable/disable resizable columns +|**`expandableRows`**|boolean|false|Enable/disable expandable +|**`renderExpandableRow `**|function||Render expandable row. `function(rowData, rowMeta) => React Component` |**`customToolbar`**|function||Render a custom toolbar |**`customToolbarSelect`**|function||Render a custom selected rows toolbar. `function(selectedRows, displayData, setSelectedRows) => void` |**`customFooter`**|function||Render a custom table footer. `function(count, page, rowsPerPage, changeRowsPerPage, changePage) => string`|` React Component` diff --git a/docs/icons/GitHub.js b/docs/icons/GitHub.js index a40a85888..0c1dd01fa 100644 --- a/docs/icons/GitHub.js +++ b/docs/icons/GitHub.js @@ -1,7 +1,7 @@ /* eslint-disable max-len */ -import React from "react"; -import SvgIcon from "@material-ui/core/SvgIcon"; +import React from 'react'; +import SvgIcon from '@material-ui/core/SvgIcon'; function GitHub(props) { return ( @@ -11,6 +11,6 @@ function GitHub(props) { ); } -GitHub.muiName = "SvgIcon"; +GitHub.muiName = 'SvgIcon'; export default GitHub; diff --git a/docs/pages/_document.js b/docs/pages/_document.js index 6eb8bea9d..7303a5305 100644 --- a/docs/pages/_document.js +++ b/docs/pages/_document.js @@ -1,7 +1,7 @@ -import React from "react"; -import Document, { Head, Main, NextScript } from "next/document"; -import JssProvider from "react-jss/lib/JssProvider"; -import getPageContext from "../utils/getPageContext"; +import React from 'react'; +import Document, { Head, Main, NextScript } from 'next/document'; +import JssProvider from 'react-jss/lib/JssProvider'; +import getPageContext from '../utils/getPageContext'; class MyDocument extends Document { render() { @@ -15,12 +15,12 @@ class MyDocument extends Document { diff --git a/docs/pages/index.js b/docs/pages/index.js index 70800a5a3..d8a566bc9 100644 --- a/docs/pages/index.js +++ b/docs/pages/index.js @@ -1,27 +1,27 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Typography from "@material-ui/core/Typography"; -import IconButton from "@material-ui/core/IconButton"; -import Tooltip from "@material-ui/core/Tooltip"; -import DownloadIcon from "@material-ui/icons/CloudDownload"; -import BuildIcon from "@material-ui/icons/Build"; // eslint-disable-line import/no-unresolved -import CodeSnippet from "../utils/CodeSnippet"; -import Layout from "../utils/layout"; -import withRoot from "../utils/withRoot"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import DownloadIcon from '@material-ui/icons/CloudDownload'; +import BuildIcon from '@material-ui/icons/Build'; // eslint-disable-line import/no-unresolved +import CodeSnippet from '../utils/CodeSnippet'; +import Layout from '../utils/layout'; +import withRoot from '../utils/withRoot'; +import { withStyles } from '@material-ui/core/styles'; const styles = theme => ({ stepIcon: { - fontSize: "30px", + fontSize: '30px', marginRight: theme.spacing.unit * 2, }, stepWrapper: { marginTop: theme.spacing.unit * 4, - display: "flex", - alignItems: "center", + display: 'flex', + alignItems: 'center', }, mainImage: { - maxWidth: "100%", + maxWidth: '100%', }, }); @@ -49,14 +49,14 @@ class Homepage extends React.Component { Installation - +
Usage
({}); @@ -17,7 +17,7 @@ class CodeSnippet extends React.Component { }; static defaultProps = { - language: "jsx", + language: 'jsx', }; render() { diff --git a/docs/utils/Menu.js b/docs/utils/Menu.js index b59d5c171..9c40b36e0 100644 --- a/docs/utils/Menu.js +++ b/docs/utils/Menu.js @@ -1,11 +1,11 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { withStyles } from "@material-ui/core"; -import Drawer from "@material-ui/core/Drawer"; -import List from "@material-ui/core/List"; -import ListItem from "@material-ui/core/ListItem"; -import ListItemText from "@material-ui/core/ListItemText"; -import ListSubheader from "@material-ui/core/ListSubheader"; +import React from 'react'; +import PropTypes from 'prop-types'; +import { withStyles } from '@material-ui/core'; +import Drawer from '@material-ui/core/Drawer'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListSubheader from '@material-ui/core/ListSubheader'; const styles = theme => ({ list: { @@ -17,18 +17,18 @@ const styles = theme => ({ }); const sandboxes = [ - { name: "Custom Component", href: "https://codesandbox.io/embed/xrvrzryjvp?autoresize=1&hidenavigation=1" }, - { name: "Customize Columns", href: "https://codesandbox.io/embed/xowj5oj8w?autoresize=1&hidenavigation=1" }, - { name: "Customize Footer", href: "https://codesandbox.io/embed/5z0w0w9jyk?autoresize=1&hidenavigation=1" }, - { name: "Customize Styling", href: "https://codesandbox.io/embed/0ylq1lqwp0?autoresize=1&hidenavigation=1" }, - { name: "Customize Toolbar", href: "https://codesandbox.io/embed/wy2rl1nyzl?autoresize=1&hidenavigation=1" }, - { name: "Customize ToolbarSelect", href: "https://codesandbox.io/embed/545ym5ov6p?autoresize=1&hidenavigation=1" }, - { name: "Resizable Columns", href: "https://codesandbox.io/embed/q8w3230qpj?autoresize=1&hidenavigation=1" }, + { name: 'Custom Component', href: 'https://codesandbox.io/embed/xrvrzryjvp?autoresize=1&hidenavigation=1' }, + { name: 'Customize Columns', href: 'https://codesandbox.io/embed/xowj5oj8w?autoresize=1&hidenavigation=1' }, + { name: 'Customize Footer', href: 'https://codesandbox.io/embed/5z0w0w9jyk?autoresize=1&hidenavigation=1' }, + { name: 'Customize Styling', href: 'https://codesandbox.io/embed/0ylq1lqwp0?autoresize=1&hidenavigation=1' }, + { name: 'Customize Toolbar', href: 'https://codesandbox.io/embed/wy2rl1nyzl?autoresize=1&hidenavigation=1' }, + { name: 'Customize ToolbarSelect', href: 'https://codesandbox.io/embed/545ym5ov6p?autoresize=1&hidenavigation=1' }, + { name: 'Resizable Columns', href: 'https://codesandbox.io/embed/q8w3230qpj?autoresize=1&hidenavigation=1' }, ]; const SandboxItem = props => ( - window.open(props.href, "_blank")} primary={props.name} /> + window.open(props.href, '_blank')} primary={props.name} /> ); diff --git a/docs/utils/getPageContext.js b/docs/utils/getPageContext.js index 9bb5e935f..8ccf31ef2 100644 --- a/docs/utils/getPageContext.js +++ b/docs/utils/getPageContext.js @@ -1,9 +1,9 @@ /* eslint-disable no-underscore-dangle */ -import { SheetsRegistry } from "jss"; -import { createMuiTheme, createGenerateClassName } from "@material-ui/core/styles"; -import purple from "@material-ui/core/colors/purple"; -import green from "@material-ui/core/colors/green"; +import { SheetsRegistry } from 'jss'; +import { createMuiTheme, createGenerateClassName } from '@material-ui/core/styles'; +import purple from '@material-ui/core/colors/purple'; +import green from '@material-ui/core/colors/green'; // A theme with custom primary and secondary color. // It's optional. diff --git a/docs/utils/layout.js b/docs/utils/layout.js index 05f18850f..fdfc9f0c0 100644 --- a/docs/utils/layout.js +++ b/docs/utils/layout.js @@ -1,47 +1,47 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Head from "next/head"; -import Typography from "@material-ui/core/Typography"; -import AppBar from "@material-ui/core/AppBar"; -import Toolbar from "@material-ui/core/Toolbar"; -import IconButton from "@material-ui/core/IconButton"; -import MenuIcon from "@material-ui/icons/Menu"; -import Tooltip from "@material-ui/core/Tooltip"; -import GitHub from "../icons/GitHub"; -import withRoot from "../utils/withRoot"; -import { withStyles } from "@material-ui/core/styles"; -import Menu from "./Menu"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Head from 'next/head'; +import Typography from '@material-ui/core/Typography'; +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import IconButton from '@material-ui/core/IconButton'; +import MenuIcon from '@material-ui/icons/Menu'; +import Tooltip from '@material-ui/core/Tooltip'; +import GitHub from '../icons/GitHub'; +import withRoot from '../utils/withRoot'; +import { withStyles } from '@material-ui/core/styles'; +import Menu from './Menu'; /* eslint-disable import/no-webpack-loader-syntax */ -import lightTheme from "!raw-loader!prismjs/themes/prism.css"; +import lightTheme from '!raw-loader!prismjs/themes/prism.css'; const styles = theme => ({ appBar: { - backgroundColor: "#23232f", + backgroundColor: '#23232f', }, toolBar: { - justifyContent: "space-between", + justifyContent: 'space-between', }, logo: { - display: "block", - height: "56px", - position: "relative", - top: "5px", + display: 'block', + height: '56px', + position: 'relative', + top: '5px', }, wrapper: { - flex: "1 0 100%", + flex: '1 0 100%', }, content: { - flex: "1 0 100%", - margin: "0 auto", - padding: "16px 16px 0px 16px", - marginTop: "64px", - minHeight: "600px", - maxWidth: "960px", + flex: '1 0 100%', + margin: '0 auto', + padding: '16px 16px 0px 16px', + marginTop: '64px', + minHeight: '600px', + maxWidth: '960px', }, footer: { - flex: "1 0 100%", - marginTop: "32px", + flex: '1 0 100%', + marginTop: '32px', }, }); @@ -51,8 +51,8 @@ class Layout extends React.Component { }; componentDidMount() { - const styleNode = document.createElement("style"); - styleNode.setAttribute("data-prism", "true"); + const styleNode = document.createElement('style'); + styleNode.setAttribute('data-prism', 'true'); if (document.head) { document.head.appendChild(styleNode); } diff --git a/docs/utils/withRoot.js b/docs/utils/withRoot.js index bdb83b9ee..d82df6ceb 100644 --- a/docs/utils/withRoot.js +++ b/docs/utils/withRoot.js @@ -1,7 +1,7 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { MuiThemeProvider } from "@material-ui/core/styles"; -import getPageContext from "./getPageContext"; +import React from 'react'; +import PropTypes from 'prop-types'; +import { MuiThemeProvider } from '@material-ui/core/styles'; +import getPageContext from './getPageContext'; function withRoot(Component) { class WithRoot extends React.Component { @@ -11,7 +11,7 @@ function withRoot(Component) { componentDidMount() { // Remove the server-side injected CSS. - const jssStyles = document.querySelector("#jss-server-side"); + const jssStyles = document.querySelector('#jss-server-side'); if (jssStyles && jssStyles.parentNode) { jssStyles.parentNode.removeChild(jssStyles); } diff --git a/examples/expandable-rows/index.js b/examples/expandable-rows/index.js new file mode 100644 index 000000000..7fa6ceed1 --- /dev/null +++ b/examples/expandable-rows/index.js @@ -0,0 +1,102 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import MUIDataTable from "../../src/"; +import TableRow from "@material-ui/core/TableRow"; +import TableCell from "@material-ui/core/TableCell"; + +class Example extends React.Component { + + render() { + + const columns = [ + { + name: "Name", + options: { + filter: true, + } + }, + { + name: "Title", + options: { + filter: true, + } + }, + { + name: "Location", + options: { + filter: false, + } + }, + { + name: "Age", + options: { + filter: true, + } + }, + { + name: "Salary", + options: { + filter: true, + sort: false + } + } + ]; + + + const data = [ + ["Gabby George", "Business Analyst", "Minneapolis", 30, "$100,000"], + ["Aiden Lloyd", "Business Consultant", "Dallas", 55, "$200,000"], + ["Jaden Collins", "Attorney", "Santa Ana", 27, "$500,000"], + ["Franky Rees", "Business Analyst", "St. Petersburg", 22, "$50,000"], + ["Aaren Rose", "Business Consultant", "Toledo", 28, "$75,000"], + ["Blake Duncan", "Business Management Analyst", "San Diego", 65, "$94,000"], + ["Frankie Parry", "Agency Legal Counsel", "Jacksonville", 71, "$210,000"], + ["Lane Wilson", "Commercial Specialist", "Omaha", 19, "$65,000"], + ["Robin Duncan", "Business Analyst", "Los Angeles", 20, "$77,000"], + ["Mel Brooks", "Business Consultant", "Oklahoma City", 37, "$135,000"], + ["Harper White", "Attorney", "Pittsburgh", 52, "$420,000"], + ["Kris Humphrey", "Agency Legal Counsel", "Laredo", 30, "$150,000"], + ["Frankie Long", "Industrial Analyst", "Austin", 31, "$170,000"], + ["Brynn Robbins", "Business Analyst", "Norfolk", 22, "$90,000"], + ["Justice Mann", "Business Consultant", "Chicago", 24, "$133,000"], + ["Addison Navarro", "Business Management Analyst", "New York", 50, "$295,000"], + ["Jesse Welch", "Agency Legal Counsel", "Seattle", 28, "$200,000"], + ["Eli Mejia", "Commercial Specialist", "Long Beach", 65, "$400,000"], + ["Gene Leblanc", "Industrial Analyst", "Hartford", 34, "$110,000"], + ["Danny Leon", "Computer Scientist", "Newark", 60, "$220,000"], + ["Lane Lee", "Corporate Counselor", "Cincinnati", 52, "$180,000"], + ["Jesse Hall", "Business Analyst", "Baltimore", 44, "$99,000"], + ["Danni Hudson", "Agency Legal Counsel", "Tampa", 37, "$90,000"], + ["Terry Macdonald", "Commercial Specialist", "Miami", 39, "$140,000"], + ["Justice Mccarthy", "Attorney", "Tucson", 26, "$330,000"], + ["Silver Carey", "Computer Scientist", "Memphis", 47, "$250,000" ], + ["Franky Miles", "Industrial Analyst", "Buffalo", 49, "$190,000"], + ["Glen Nixon", "Corporate Counselor", "Arlington", 44, "$80,000"], + ["Gabby Strickland", "Business Process Consultant", "Scottsdale", 26, "$45,000"], + ["Mason Ray", "Computer Scientist", "San Francisco", 39, "$142,000"] + ]; + + const options = { + filter: true, + filterType: 'dropdown', + responsive: 'scroll', + expandableRows: true, + renderExpandableRow: (rowData, rowMeta) => { + return ( + + + Custom expandable row option. Data: {JSON.stringify(rowData)} + + + ); + } + }; + + return ( + + ); + + } +} + +ReactDOM.render(, document.getElementById("app-root")); diff --git a/prettier.config.js b/prettier.config.js index 5b7d82a90..93f6ee64e 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1,6 +1,6 @@ module.exports = { printWidth: 120, - singleQuote: false, + singleQuote: true, trailingComma: 'all', bracketSpacing: true, jsxBracketSameLine: true, diff --git a/src/MUIDataTable.js b/src/MUIDataTable.js index d756b1dae..b0dbb76aa 100644 --- a/src/MUIDataTable.js +++ b/src/MUIDataTable.js @@ -1,40 +1,43 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Paper from "@material-ui/core/Paper"; -import Table from "@material-ui/core/Table"; -import MUIDataTableToolbar from "./MUIDataTableToolbar"; -import MUIDataTableToolbarSelect from "./MUIDataTableToolbarSelect"; -import MUIDataTableFilterList from "./MUIDataTableFilterList"; -import MUIDataTableBody from "./MUIDataTableBody"; -import MUIDataTableResize from "./MUIDataTableResize"; -import MUIDataTableHead from "./MUIDataTableHead"; -import MUIDataTablePagination from "./MUIDataTablePagination"; -import cloneDeep from "lodash.clonedeep"; -import merge from "lodash.merge"; -import isEqual from "lodash.isequal"; -import textLabels from "./textLabels"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Paper from '@material-ui/core/Paper'; +import MuiTable from '@material-ui/core/Table'; +import TableToolbar from './components/TableToolbar'; +import TableToolbarSelect from './components/TableToolbarSelect'; +import TableFilterList from './components/TableFilterList'; +import TableBody from './components/TableBody'; +import TableResize from './components/TableResize'; +import TableHead from './components/TableHead'; +import TableFooter from './components/TableFooter'; +import TablePagination from './components/TablePagination'; +import cloneDeep from 'lodash.clonedeep'; +import merge from 'lodash.merge'; +import isEqual from 'lodash.isequal'; +import textLabels from './textLabels'; +import { withStyles } from '@material-ui/core/styles'; +import { buildMap, getCollatorComparator, sortCompare } from './utils'; const defaultTableStyles = { root: {}, responsiveScroll: { - overflow: "auto", - height: "100%", - maxHeight: "499px", + overflowX: 'auto', + overflow: 'auto', + height: '100%', + maxHeight: '499px', }, caption: { - position: "absolute", - left: "-3000px", + position: 'absolute', + left: '-3000px', }, liveAnnounce: { - border: "0", - clip: "rect(0 0 0 0)", - height: "1px", - margin: "-1px", - overflow: "hidden", - padding: "0", - position: "absolute", - width: "1px", + border: '0', + clip: 'rect(0 0 0 0)', + height: '1px', + margin: '-1px', + overflow: 'hidden', + padding: '0', + position: 'absolute', + width: '1px', }, }; @@ -68,10 +71,12 @@ class MUIDataTable extends React.Component { ).isRequired, /** Options used to describe table */ options: PropTypes.shape({ - responsive: PropTypes.oneOf(["stacked", "scroll"]), - filterType: PropTypes.oneOf(["dropdown", "checkbox", "multiselect"]), + responsive: PropTypes.oneOf(['stacked', 'scroll']), + filterType: PropTypes.oneOf(['dropdown', 'checkbox', 'multiselect']), textLabels: PropTypes.object, pagination: PropTypes.bool, + expandableRows: PropTypes.bool, + renderExpandableRow: PropTypes.func, customToolbar: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), customToolbarSelect: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), customFooter: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), @@ -106,7 +111,7 @@ class MUIDataTable extends React.Component { }; static defaultProps = { - title: "", + title: '', options: {}, data: [], columns: [], @@ -126,6 +131,10 @@ class MUIDataTable extends React.Component { data: [], lookup: {}, }, + expandedRows: { + data: [], + lookup: {}, + }, showResponsive: false, searchText: null, }; @@ -158,26 +167,16 @@ class MUIDataTable extends React.Component { this.setTableData(props, TABLE_LOAD.INITIAL); } - static fallbackComparator = (a, b) => a.localeCompare(b); - - static getCollatzComparator = () => { - if (!!Intl) { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" }); - return collator.compare; - } - - return MUIDataTable.fallbackComparator; - }; - /* * React currently does not support deep merge for defaultProps. Objects are overwritten */ getDefaultOptions(props) { const defaultOptions = { - responsive: "stacked", - filterType: "checkbox", + responsive: 'stacked', + filterType: 'checkbox', pagination: true, textLabels, + expandableRows: false, resizableColumns: false, selectableRows: true, caseSensitive: false, @@ -194,8 +193,8 @@ class MUIDataTable extends React.Component { viewColumns: true, download: true, downloadOptions: { - filename: "tableDownload.csv", - separator: ",", + filename: 'tableDownload.csv', + separator: ',', }, }; @@ -204,18 +203,21 @@ class MUIDataTable extends React.Component { validateOptions(options) { if (options.serverSide && options.onTableChange === undefined) { - throw Error("onTableChange callback must be provided when using serverSide option"); + throw Error('onTableChange callback must be provided when using serverSide option'); + } + if (options.expandableRows && options.renderExpandableRow === undefined) { + throw Error('renderExpandableRow must be provided when using expandableRows option'); } } setTableAction = action => { - if (typeof this.options.onTableChange === "function") { + if (typeof this.options.onTableChange === 'function') { this.options.onTableChange(action, this.state); } }; setTableOptions(props) { - const optionNames = ["rowsPerPage", "page", "rowsSelected", "filterList", "rowsPerPageOptions"]; + const optionNames = ['rowsPerPage', 'page', 'rowsSelected', 'filterList', 'rowsPerPageOptions']; const optState = optionNames.reduce((acc, cur) => { if (this.options[cur] !== undefined) { acc[cur] = this.options[cur]; @@ -231,9 +233,13 @@ class MUIDataTable extends React.Component { this.headCellRefs[index] = el; }; + getTableContentRef = () => { + return this.tableContent.current; + }; + rawColumns = cols => { return cols.map(item => { - if (typeof item !== "object") return item; + if (typeof item !== 'object') return item; const { options, ...otherOpts } = item; return otherOpts; @@ -256,14 +262,14 @@ class MUIDataTable extends React.Component { newColumns.forEach((column, colIndex) => { let columnOptions = { - display: "true", + display: 'true', filter: true, sort: true, download: true, sortDirection: null, }; - if (typeof column === "object") { + if (typeof column === 'object') { if (column.options && column.options.display !== undefined) { column.options.display = column.options.display.toString(); } @@ -298,20 +304,20 @@ class MUIDataTable extends React.Component { for (let rowIndex = 0; rowIndex < data.length; rowIndex++) { let value = status === TABLE_LOAD.INITIAL ? data[rowIndex][colIndex] : data[rowIndex].data[colIndex]; - if (typeof tableData[rowIndex] === "undefined") { + if (typeof tableData[rowIndex] === 'undefined') { tableData.push({ index: status === TABLE_LOAD.INITIAL ? rowIndex : data[rowIndex].index, data: status === TABLE_LOAD.INITIAL ? data[rowIndex] : data[rowIndex].data, }); } - if (typeof column.customBodyRender === "function") { + if (typeof column.customBodyRender === 'function') { const tableMeta = this.getTableMeta(rowIndex, colIndex, value, [], column, this.state); const funcResult = column.customBodyRender(value, tableMeta); if (React.isValidElement(funcResult) && funcResult.props.value) { value = funcResult.props.value; - } else if (typeof funcResult === "string") { + } else if (typeof funcResult === 'string') { value = funcResult; } } @@ -320,20 +326,20 @@ class MUIDataTable extends React.Component { } if (this.options.sortFilterList) { - const comparator = MUIDataTable.getCollatzComparator(); + const comparator = getCollatorComparator(); filterData[colIndex].sort(comparator); } if (column.sortDirection !== null) { sortIndex = colIndex; - sortDirection = column.sortDirection === "asc" ? "desc" : "asc"; + sortDirection = column.sortDirection === 'asc' ? 'desc' : 'asc'; } }); if (options.filterList) filterList = options.filterList; if (filterList.length !== columns.length) { - throw new Error("Provided options.filterList does not match the column length"); + throw new Error('Provided options.filterList does not match the column length'); } let selectedRowsData = { @@ -397,7 +403,7 @@ class MUIDataTable extends React.Component { /* drill down to get the value of a cell */ columnValue = - typeof funcResult === "string" + typeof funcResult === 'string' ? funcResult : funcResult.props && funcResult.props.value ? funcResult.props.value @@ -410,7 +416,7 @@ class MUIDataTable extends React.Component { isFiltered = true; } - const columnVal = columnValue === null ? "" : columnValue.toString(); + const columnVal = columnValue === null ? '' : columnValue.toString(); if (searchText) { let searchNeedle = searchText.toString(); @@ -442,7 +448,7 @@ class MUIDataTable extends React.Component { const filterValue = React.isValidElement(funcResult) && funcResult.props.value ? funcResult.props.value - : prevState["data"][row][index]; + : prevState['data'][row][index]; const prevFilterIndex = filterData[index].indexOf(filterValue); filterData[index].splice(prevFilterIndex, 1, filterValue); @@ -450,7 +456,7 @@ class MUIDataTable extends React.Component { changedData[row].data[index] = value; if (this.options.sortFilterList) { - const comparator = MUIDataTable.getCollatzComparator(); + const comparator = getCollatorComparator(); filterData[index].sort(comparator); } @@ -497,17 +503,17 @@ class MUIDataTable extends React.Component { this.setState( prevState => { const columns = cloneDeep(prevState.columns); - columns[index].display = columns[index].display === "true" ? "false" : "true"; + columns[index].display = columns[index].display === 'true' ? 'false' : 'true'; return { columns: columns, }; }, () => { - this.setTableAction("columnViewChange"); + this.setTableAction('columnViewChange'); if (this.options.onColumnViewChange) { this.options.onColumnViewChange( this.state.columns[index].name, - this.state.columns[index].display === "true" ? "add" : "remove", + this.state.columns[index].display === 'true' ? 'add' : 'remove', ); } }, @@ -515,7 +521,7 @@ class MUIDataTable extends React.Component { }; getSortDirection(column) { - return column.sortDirection === "asc" ? "ascending" : "descending"; + return column.sortDirection === 'asc' ? 'ascending' : 'descending'; } toggleSortColumn = index => { @@ -529,7 +535,7 @@ class MUIDataTable extends React.Component { if (index !== pos) { columns[pos].sortDirection = null; } else { - columns[pos].sortDirection = columns[pos].sortDirection === "asc" ? "desc" : "asc"; + columns[pos].sortDirection = columns[pos].sortDirection === 'asc' ? 'desc' : 'asc'; } } @@ -563,7 +569,7 @@ class MUIDataTable extends React.Component { return newState; }, () => { - this.setTableAction("sort"); + this.setTableAction('sort'); if (this.options.onColumnSortChange) { this.options.onColumnSortChange( this.state.columns[index].name, @@ -588,7 +594,7 @@ class MUIDataTable extends React.Component { page: this.state.page > nextTotalPages ? nextTotalPages : this.state.page, }), () => { - this.setTableAction("changeRowsPerPage"); + this.setTableAction('changeRowsPerPage'); if (this.options.onChangeRowsPerPage) { this.options.onChangeRowsPerPage(this.state.rowsPerPage); } @@ -602,7 +608,7 @@ class MUIDataTable extends React.Component { page: page, }), () => { - this.setTableAction("changePage"); + this.setTableAction('changePage'); if (this.options.onChangePage) { this.options.onChangePage(this.state.page); } @@ -619,7 +625,7 @@ class MUIDataTable extends React.Component { : this.getDisplayData(prevState.columns, prevState.data, prevState.filterList, text), }), () => { - this.setTableAction("search"); + this.setTableAction('search'); }, ); }; @@ -637,7 +643,7 @@ class MUIDataTable extends React.Component { }; }, () => { - this.setTableAction("resetFilters"); + this.setTableAction('resetFilters'); if (this.options.onFilterChange) { this.options.onFilterChange(null, this.state.filterList); } @@ -652,14 +658,14 @@ class MUIDataTable extends React.Component { const filterPos = filterList[index].indexOf(column); switch (type) { - case "checkbox": + case 'checkbox': filterPos >= 0 ? filterList[index].splice(filterPos, 1) : filterList[index].push(column); break; - case "multiselect": - filterList[index] = column === "" ? [] : column; + case 'multiselect': + filterList[index] = column === '' ? [] : column; break; default: - filterList[index] = filterPos >= 0 || column === "" ? [] : [column]; + filterList[index] = filterPos >= 0 || column === '' ? [] : [column]; } return { @@ -670,7 +676,7 @@ class MUIDataTable extends React.Component { }; }, () => { - this.setTableAction("filterChange"); + this.setTableAction('filterChange'); if (this.options.onFilterChange) { this.options.onFilterChange(column, this.state.filterList); } @@ -681,7 +687,7 @@ class MUIDataTable extends React.Component { selectRowDelete = () => { const { selectedRows, data, filterList } = this.state; - const selectedMap = this.buildSelectedMap(selectedRows.data); + const selectedMap = buildMap(selectedRows.data); const cleanRows = data.filter(({ index }) => !selectedMap[index]); if (this.options.onRowsDelete) { @@ -698,20 +704,44 @@ class MUIDataTable extends React.Component { }, TABLE_LOAD.UPDATE, () => { - this.setTableAction("rowDelete"); + this.setTableAction('rowDelete'); }, ); }; - buildSelectedMap = rows => { - return rows.reduce((accum, { dataIndex }) => { - accum[dataIndex] = true; - return accum; - }, {}); + toggleExpandRow = row => { + const { index, dataIndex } = row; + let expandedRows = [...this.state.expandedRows.data]; + let rowPos = -1; + + for (let cIndex = 0; cIndex < expandedRows.length; cIndex++) { + if (expandedRows[cIndex].index === index) { + rowPos = cIndex; + break; + } + } + + if (rowPos >= 0) { + expandedRows.splice(rowPos, 1); + } else { + expandedRows.push(row); + } + + this.setState( + { + expandedRows: { + lookup: buildMap(expandedRows), + data: expandedRows, + }, + }, + () => { + this.setTableAction('expandRow'); + }, + ); }; selectRowUpdate = (type, value) => { - if (type === "head") { + if (type === 'head') { this.setState( prevState => { const { displayData } = prevState; @@ -726,11 +756,11 @@ class MUIDataTable extends React.Component { .map((d, i) => ({ index: i, dataIndex: displayData[i].dataIndex })); let newRows = [...prevState.selectedRows, ...selectedRows]; - let selectedMap = this.buildSelectedMap(newRows); + let selectedMap = buildMap(newRows); if (isDeselect) { newRows = prevState.selectedRows.data.filter(({ dataIndex }) => !selectedMap[dataIndex]); - selectedMap = this.buildSelectedMap(newRows); + selectedMap = buildMap(newRows); } return { @@ -742,13 +772,13 @@ class MUIDataTable extends React.Component { }; }, () => { - this.setTableAction("rowsSelect"); + this.setTableAction('rowsSelect'); if (this.options.onRowsSelect) { this.options.onRowsSelect(this.state.curSelectedRows, this.state.selectedRows.data); } }, ); - } else if (type === "cell") { + } else if (type === 'cell') { this.setState( prevState => { const { index, dataIndex } = value; @@ -770,30 +800,30 @@ class MUIDataTable extends React.Component { return { selectedRows: { - lookup: this.buildSelectedMap(selectedRows), + lookup: buildMap(selectedRows), data: selectedRows, }, }; }, () => { - this.setTableAction("rowsSelect"); + this.setTableAction('rowsSelect'); if (this.options.onRowsSelect) { this.options.onRowsSelect([value], this.state.selectedRows.data); } }, ); - } else if (type === "custom") { + } else if (type === 'custom') { const { displayData } = this.state; const data = value.map(row => ({ index: row, dataIndex: displayData[row].dataIndex })); - const lookup = this.buildSelectedMap(data); + const lookup = buildMap(data); this.setState( { selectedRows: { data, lookup }, }, () => { - this.setTableAction("rowsSelect"); + this.setTableAction('rowsSelect'); if (this.options.onRowsSelect) { this.options.onRowsSelect(this.state.selectedRows.data, this.state.selectedRows.data); } @@ -802,19 +832,8 @@ class MUIDataTable extends React.Component { } }; - sortCompare(order) { - return (a, b) => { - if (a.data === null) a.data = ""; - if (b.data === null) b.data = ""; - return ( - (typeof a.data.localeCompare === "function" ? a.data.localeCompare(b.data) : a.data - b.data) * - (order === "asc" ? -1 : 1) - ); - }; - } - sortTable(data, col, order) { - let dataSrc = this.options.customSort ? this.options.customSort(data, col, order || "desc") : data; + let dataSrc = this.options.customSort ? this.options.customSort(data, col, order || 'desc') : data; let sortedData = dataSrc.map((row, sIndex) => ({ data: row.data[col], @@ -823,7 +842,7 @@ class MUIDataTable extends React.Component { })); if (!this.options.customSort) { - sortedData.sort(this.sortCompare(order)); + sortedData.sort(sortCompare(order)); } let tableData = []; @@ -840,7 +859,7 @@ class MUIDataTable extends React.Component { return { data: tableData, selectedRows: { - lookup: this.buildSelectedMap(selectedRows), + lookup: buildMap(selectedRows), data: selectedRows, }, }; @@ -865,6 +884,7 @@ class MUIDataTable extends React.Component { filterList, rowsPerPage, selectedRows, + expandedRows, searchText, } = this.state; @@ -873,7 +893,7 @@ class MUIDataTable extends React.Component { return ( {selectedRows.data.length ? ( - ) : ( - )} - +
+ style={{ position: 'relative' }} + className={this.options.responsive === 'scroll' ? classes.responsiveScroll : null}> {this.options.resizableColumns && ( - (this.setHeadResizeable = fn)} /> + (this.setHeadResizeable = fn)} /> )} - (this.tableRef = el)} tabIndex={"0"} role={"grid"}> + (this.tableRef = el)} tabIndex={'0'} role={'grid'}> - - -
{title}
+
- - {this.options.customFooter - ? this.options.customFooter(rowCount, page, rowsPerPage, this.changeRowsPerPage, this.changePage) - : this.options.pagination && ( - - )} -
-
(this.announceRef = el)}> + +
(this.announceRef = el)}> {announceText}
@@ -958,4 +973,4 @@ class MUIDataTable extends React.Component { } } -export default withStyles(defaultTableStyles, { name: "MUIDataTable" })(MUIDataTable); +export default withStyles(defaultTableStyles, { name: 'MUIDataTable' })(MUIDataTable); diff --git a/src/MUIDataTableBody.js b/src/MUIDataTableBody.js deleted file mode 100644 index c1885edbb..000000000 --- a/src/MUIDataTableBody.js +++ /dev/null @@ -1,156 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Typography from "@material-ui/core/Typography"; -import TableBody from "@material-ui/core/TableBody"; -import MUIDataTableBodyCell from "./MUIDataTableBodyCell"; -import MUIDataTableBodyRow from "./MUIDataTableBodyRow"; -import MUIDataTableSelectCell from "./MUIDataTableSelectCell"; -import { withStyles } from "@material-ui/core/styles"; - -const defaultBodyStyles = { - root: {}, - emptyTitle: { - textAlign: "center", - }, -}; - -class MUIDataTableBody extends React.Component { - static propTypes = { - /** Data used to describe table */ - data: PropTypes.array.isRequired, - /** Total number of data rows */ - count: PropTypes.number.isRequired, - /** Columns used to describe table */ - columns: PropTypes.array.isRequired, - /** Options used to describe table */ - options: PropTypes.object.isRequired, - /** Data used to filter table against */ - filterList: PropTypes.array, - /** Callback to execute when row is clicked */ - onRowClick: PropTypes.func, - /** Table rows selected */ - selectedRows: PropTypes.object, - /** Callback to trigger table row select */ - selectRowUpdate: PropTypes.func, - /** Data used to search table against */ - searchText: PropTypes.string, - /** Extend the style applied to components */ - classes: PropTypes.object, - }; - - buildRows() { - const { data, page, rowsPerPage, count } = this.props; - - if (this.props.options.serverSide) return data; - - let rows = []; - const totalPages = Math.floor(count / rowsPerPage); - const fromIndex = page === 0 ? 0 : page * rowsPerPage; - const toIndex = Math.min(count, (page + 1) * rowsPerPage); - - if (page > totalPages && totalPages !== 0) { - throw new Error( - "Provided options.page of `" + - page + - "` is greater than the total available page length of `" + - totalPages + - "`", - ); - } - - for (let rowIndex = fromIndex; rowIndex < count && rowIndex < toIndex; rowIndex++) { - if (data[rowIndex] !== undefined) rows.push(data[rowIndex]); - } - - return rows.length ? rows : null; - } - - getRowIndex(index) { - const { page, rowsPerPage, options } = this.props; - - if (options.serverSide) { - return index; - } - - const startIndex = page === 0 ? 0 : page * rowsPerPage; - return startIndex + index; - } - - isRowSelected(dataIndex) { - const { selectedRows } = this.props; - return selectedRows.lookup && selectedRows.lookup[dataIndex] ? true : false; - } - - handleRowSelect = data => { - this.props.selectRowUpdate("cell", data); - }; - - render() { - const { classes, columns, options } = this.props; - const tableRows = this.buildRows(); - - if (tableRows) { - return ( - - {tableRows.map(({ data: row, dataIndex }, rowIndex) => ( - - {options.selectableRows && ( - - )} - {row.map( - (column, columnIndex) => - columns[columnIndex].display === "true" && ( - - {column} - - ), - )} - - ))} - - ); - } - - return ( - - { - - - - {options.textLabels.body.noMatch} - - - - } - - ); - } -} - -export default withStyles(defaultBodyStyles, { name: "MUIDataTableBody" })(MUIDataTableBody); diff --git a/src/MUIDataTableSelectCell.js b/src/MUIDataTableSelectCell.js deleted file mode 100644 index e19f7cb44..000000000 --- a/src/MUIDataTableSelectCell.js +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import classNames from "classnames"; -import Checkbox from "@material-ui/core/Checkbox"; -import TableCell from "@material-ui/core/TableCell"; -import { withStyles } from "@material-ui/core/styles"; - -const defaultSelectCellStyles = theme => ({ - root: { - [theme.breakpoints.down("sm")]: { - display: "none", - }, - }, - fixedHeader: { - position: "sticky", - top: "0px", - left: "0px", - zIndex: 100, - backgroundColor: "#FFF", - }, - headerCell: { - zIndex: 110, - }, - checkboxRoot: { - "&$checked": { - color: "#027cb5", - }, - }, - checked: {}, - disabled: {}, -}); - -class MUIDataTableSelectCell extends React.Component { - static propTypes = { - /** Select cell checked on/off */ - checked: PropTypes.bool.isRequired, - /** Select cell part of fixed header */ - fixedHeader: PropTypes.bool.isRequired, - /** Callback to trigger cell update */ - onChange: PropTypes.func, - /** Extend the style applied to components */ - classes: PropTypes.object, - }; - - static defaultProps = { - isHeaderCell: false, - }; - - render() { - const { classes, fixedHeader, isHeaderCell, ...otherProps } = this.props; - - const cellClass = classNames({ - [classes.root]: true, - [classes.fixedHeader]: fixedHeader, - [classes.headerCell]: isHeaderCell, - }); - - return ( - - - - ); - } -} - -export default withStyles(defaultSelectCellStyles, { name: "MUIDataTableSelectCell" })(MUIDataTableSelectCell); diff --git a/src/MUIDataTableToolbar.js b/src/MUIDataTableToolbar.js deleted file mode 100644 index ee36f9360..000000000 --- a/src/MUIDataTableToolbar.js +++ /dev/null @@ -1,268 +0,0 @@ -import React from "react"; -import Typography from "@material-ui/core/Typography"; -import Toolbar from "@material-ui/core/Toolbar"; -import Tooltip from "@material-ui/core/Tooltip"; -import IconButton from "@material-ui/core/IconButton"; -import MUIDataTableFilter from "./MUIDataTableFilter"; -import MUIDataTableViewCol from "./MUIDataTableViewCol"; -import MUIDataTableSearch from "./MUIDataTableSearch"; -import SearchIcon from "@material-ui/icons/Search"; -import DownloadIcon from "@material-ui/icons/CloudDownload"; -import PrintIcon from "@material-ui/icons/Print"; -import ViewColumnIcon from "@material-ui/icons/ViewColumn"; -import FilterIcon from "@material-ui/icons/FilterList"; -import ReactToPrint from "react-to-print"; -import styled from "./styled"; -import MUIDataTablePopoverWrapper from "./MUIPopover/MUIDataTablePopoverWrapper"; - -export const defaultToolbarStyles = (theme, props) => ({ - root: {}, - left: { - flex: "1 1 55%", - }, - actions: { - flex: "0 0 45%", - textAlign: "right", - }, - titleRoot: {}, - titleText: {}, - icon: { - "&:hover": { - color: "#307BB0", - }, - }, - iconActive: { - color: "#307BB0", - }, - searchIcon: { - display: "inline-flex", - marginTop: "10px", - marginRight: "8px", - }, - ...(props.options.responsive ? { ...responsiveToolbarStyles(theme) } : {}), -}); - -export const responsiveToolbarStyles = theme => ({ - [theme.breakpoints.down("sm")]: { - titleRoot: {}, - titleText: { - fontSize: "16px", - }, - spacer: { - display: "none", - }, - left: { - // flex: "1 1 40%", - padding: "8px 0px", - }, - actions: { - // flex: "1 1 60%", - textAlign: "right", - }, - }, - [theme.breakpoints.down("xs")]: { - root: { - display: "block", - }, - left: { - padding: "8px 0px 0px 0px", - }, - titleText: { - textAlign: "center", - }, - actions: { - textAlign: "center", - }, - }, - "@media screen and (max-width: 480px)": {}, -}); - -class MUIDataTableToolbar extends React.Component { - state = { - iconActive: null, - showSearch: false, - }; - - handleCSVDownload = () => { - const { data, columns, options } = this.props; - - const CSVHead = - columns - .reduce( - (soFar, column) => - column.download ? soFar + '"' + column.name + '"' + options.downloadOptions.separator : soFar, - "", - ) - .slice(0, -1) + "\r\n"; - - const CSVBody = data - .reduce( - (soFar, row) => - soFar + - '"' + - row.data - .filter((field, index) => columns[index].download) - .join('"' + options.downloadOptions.separator + '"') + - '"\r\n', - [], - ) - .trim(); - - /* taken from react-csv */ - const csv = `${CSVHead}${CSVBody}`; - const blob = new Blob([csv], { type: "text/csv" }); - - if (navigator && navigator.msSaveOrOpenBlob) { - navigator.msSaveOrOpenBlob(blob, options.downloadOptions.filename); - } else { - const dataURI = `data:text/csv;charset=utf-8,${csv}`; - - const URL = window.URL || window.webkitURL; - const downloadURI = typeof URL.createObjectURL === "undefined" ? dataURI : URL.createObjectURL(blob); - - let link = document.createElement("a"); - link.setAttribute("href", downloadURI); - link.setAttribute("download", options.downloadOptions.filename); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } - }; - - setActiveIcon = iconName => { - this.setState(() => ({ - iconActive: iconName, - showSearch: iconName === "search" ? this.handleShowSearch() : false, - })); - }; - - getActiveIcon = (styles, iconName) => { - return this.state.iconActive !== iconName ? styles.icon : styles.iconActive; - }; - - handleShowSearch = () => { - !!this.props.options.onSearchOpen && this.props.options.onSearchOpen(); - this.props.setTableAction("onSearchOpen"); - return true; - }; - - hideSearch = () => { - const { onSearchClose } = this.props.options; - - if (onSearchClose) onSearchClose(); - this.props.searchTextUpdate(null); - - this.setState(() => ({ - iconActive: null, - showSearch: false, - })); - - this.searchButton.focus(); - }; - - render() { - const { - data, - options, - classes, - columns, - filterData, - filterList, - filterUpdate, - resetFilters, - searchTextUpdate, - toggleViewColumn, - title, - tableRef, - } = this.props; - - const { search, downloadCsv, print, viewColumns, filterTable } = options.textLabels.toolbar; - const { showSearch } = this.state; - - return ( - -
- {showSearch === true ? ( - - ) : ( -
- - {title} - -
- )} -
-
- {options.search && ( - - (this.searchButton = el)} - classes={{ root: this.getActiveIcon(classes, "search") }} - onClick={this.setActiveIcon.bind(null, "search")}> - - - - )} - - {options.download && ( - - - - - - )} - - {options.print && ( - - - ( - - - - )} - content={() => this.props.tableRef()} - /> - - - )} - - {options.viewColumns && ( - } - classes={classes}> - - - )} - - {options.filter && ( - } - classes={classes}> - - - )} - {options.customToolbar ? options.customToolbar() : false} -
-
- ); - } -} - -export default styled(MUIDataTableToolbar)(defaultToolbarStyles, { name: "MUIDataTableToolbar" }); diff --git a/src/MUIPopover/MUIDataTablePopoverWrapper.js b/src/MUIPopover/MUIDataTablePopoverWrapper.js deleted file mode 100644 index b5ac50aa1..000000000 --- a/src/MUIPopover/MUIDataTablePopoverWrapper.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import Tooltip from "@material-ui/core/Tooltip"; -import IconButton from "@material-ui/core/IconButton"; -import MUIPopover from "./MUIPopover"; -import MUIPopoverTarget from "./MUIPopoverTarget"; -import MUIPopoverContent from "./MUIPopoverContent"; - -class MUIDataTablePopoverWrapper extends React.PureComponent { - constructor(props) { - super(props); - } - render() { - const { label, tableRef, onClick, icon, children } = this.props; - - return ( - - - - {icon} - - - {children} - - ); - } -} - -export default MUIDataTablePopoverWrapper; diff --git a/src/MUIPopover/MUIPopover.js b/src/MUIPopover/MUIPopover.js deleted file mode 100644 index 9aeed724f..000000000 --- a/src/MUIPopover/MUIPopover.js +++ /dev/null @@ -1,128 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Popover from "@material-ui/core/Popover"; -import MUIPopoverContent from "./MUIPopoverContent"; -import MUIPopoverTarget from "./MUIPopoverTarget"; -import { findDOMNode } from "react-dom"; - -class MUIPopover extends React.Component { - static propTypes = { - /** Show indicating arrow. default: true */ - arrow: PropTypes.bool, - /** Reference callback to handleRequestClose() to trigger manual close of MUIPopover */ - refClose: PropTypes.func, - /** Reference callback to onExited() to trigger manual close of MUIPopover */ - refExit: PropTypes.func, - /** MUIPopoverTarget and MUIPopoverContent are required children */ - children: (props, propName, componentName) => { - let childMatch = true; - const expectedComponents = [MUIPopoverContent, MUIPopoverTarget]; - - React.Children.map(props.children, (child, index) => { - if (expectedComponents.indexOf(child.type) === -1) childMatch = false; - }); - - if (!childMatch) { - return new Error( - "`" + - componentName + - "` " + - "should only have children of the following types: `MUIPopoverTarget`, `MUIPopoverContent`.", - ); - } - }, - }; - - state = { - open: false, - }; - - componentWillMount() { - this.anchorEl = null; - } - - componentDidMount() { - /* - * expose close method to the parent - */ - if (this.props.refClose) { - this.props.refClose(this.handleRequestClose); - } - } - - componentDidUpdate(prevProps, prevState) { - /* - * Update Popover position if a filter removes data from the table because - * it affects the window height which would cause the Popover to in the wrong place - */ - if (this.state.open === true) { - this.anchorEl = findDOMNode(this.anchorEl); - this.popoverActions.updatePosition(); - } - } - - handleClick = () => { - this.anchorEl = findDOMNode(this.anchorEl); - this.setState({ open: true }); - }; - - handleRequestClose = cb => { - this.setState({ open: false }, cb && typeof cb === "function" ? cb() : () => {}); - }; - - handleOnExit = () => { - if (this.props.refExit) { - this.props.refExit(); - } - }; - - render() { - let popoverRender = []; - - const { className, placement, refClose, refExit, children, ...providedProps } = this.props; - - React.Children.map(children, (child, index) => { - if (child.type === MUIPopoverContent || child.type === .type) { - const transformOriginSpecs = { - vertical: "top", - horizontal: "center", - }; - - const anchorOriginSpecs = { - vertical: "bottom", - horizontal: "center", - }; - - const popoverContent = ( - (this.popoverActions = actions)} - key={index} - elevation={2} - open={this.state.open} - onClose={this.handleRequestClose} - onExited={this.handleOnExit} - anchorEl={this.anchorEl} - anchorOrigin={anchorOriginSpecs} - transformOrigin={transformOriginSpecs} - {...providedProps}> - {child} - - ); - - popoverRender.push(popoverContent); - } else if (child.type === MUIPopoverTarget || child.type === .type) { - const targetContent = React.cloneElement(child, { - key: index, - targetRef: el => (this.anchorEl = el), - onClick: this.handleClick, - }); - - popoverRender.push(targetContent); - } - }); - - return popoverRender; - } -} - -export default MUIPopover; diff --git a/src/MUIPopover/MUIPopoverContent.js b/src/MUIPopover/MUIPopoverContent.js deleted file mode 100644 index 00cd90dcd..000000000 --- a/src/MUIPopover/MUIPopoverContent.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -class MUIPopoverContent extends React.Component { - render() { - return this.props.children; - } -} - -export default MUIPopoverContent; diff --git a/src/MUIPopover/MUIPopoverTarget.js b/src/MUIPopover/MUIPopoverTarget.js deleted file mode 100644 index 5a5d187e2..000000000 --- a/src/MUIPopover/MUIPopoverTarget.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react"; - -class MUIPopoverTarget extends React.Component { - render() { - const targetContent = React.Children.map(this.props.children, (child, index) => { - return React.cloneElement(child, { - key: index, - ref: this.props.targetRef, - onClick: this.props.onClick, - }); - }); - - return targetContent; - } -} - -export default MUIPopoverTarget; diff --git a/src/MUIPopover/index.js b/src/MUIPopover/index.js deleted file mode 100644 index 71cff317a..000000000 --- a/src/MUIPopover/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import MUIPopover from "./MUIPopover"; -import MUIPopoverContent from "./MUIPopoverContent"; -import MUIPopoverTarget from "./MUIPopoverTarget"; - -export { MUIPopover, MUIPopoverContent, MUIPopoverTarget }; diff --git a/src/components/Popover.js b/src/components/Popover.js new file mode 100644 index 000000000..923ce4212 --- /dev/null +++ b/src/components/Popover.js @@ -0,0 +1,90 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import MuiPopover from '@material-ui/core/Popover'; +import { findDOMNode } from 'react-dom'; + +class Popover extends React.Component { + state = { + open: false, + }; + + componentWillMount() { + this.anchorEl = null; + } + + componentDidMount() { + if (this.props.refClose) { + this.props.refClose(this.handleRequestClose); + } + } + + componentDidUpdate(prevProps, prevState) { + /* + * Update Popover position if a filter removes data from the table because + * it affects the window height which would cause the Popover to in the wrong place + */ + if (this.state.open === true) { + this.anchorEl = findDOMNode(this.anchorEl); + this.popoverActions.updatePosition(); + } + } + + handleClick = () => { + this.anchorEl = findDOMNode(this.anchorEl); + this.setState({ open: true }); + }; + + handleRequestClose = cb => { + this.setState({ open: false }, cb && typeof cb === 'function' ? cb() : () => {}); + }; + + handleOnExit = () => { + if (this.props.refExit) { + this.props.refExit(); + } + }; + + render() { + const { className, placement, trigger, refExit, content, ...providedProps } = this.props; + + const transformOriginSpecs = { + vertical: 'top', + horizontal: 'center', + }; + + const anchorOriginSpecs = { + vertical: 'bottom', + horizontal: 'center', + }; + + const triggerEl = React.cloneElement(trigger, { + key: 'content', + ref: el => (this.anchorEl = el), + onClick: () => { + if (trigger.props.onClick) trigger.props.onClick(); + this.handleClick(); + }, + }); + + return ( + + (this.popoverActions = actions)} + elevation={2} + open={this.state.open} + onClose={this.handleRequestClose} + onExited={this.handleOnExit} + anchorEl={this.anchorEl} + ref={el => this.popoverEl} + anchorOrigin={anchorOriginSpecs} + transformOrigin={transformOriginSpecs} + {...providedProps}> + {content} + + {triggerEl} + + ); + } +} + +export default Popover; diff --git a/src/components/TableBody.js b/src/components/TableBody.js new file mode 100644 index 000000000..701d8e56d --- /dev/null +++ b/src/components/TableBody.js @@ -0,0 +1,169 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Typography from '@material-ui/core/Typography'; +import MuiTableBody from '@material-ui/core/TableBody'; +import TableBodyCell from './TableBodyCell'; +import TableBodyRow from './TableBodyRow'; +import TableSelectCell from './TableSelectCell'; +import { withStyles } from '@material-ui/core/styles'; + +const defaultBodyStyles = { + root: {}, + emptyTitle: { + textAlign: 'center', + }, +}; + +class TableBody extends React.Component { + static propTypes = { + /** Data used to describe table */ + data: PropTypes.array.isRequired, + /** Total number of data rows */ + count: PropTypes.number.isRequired, + /** Columns used to describe table */ + columns: PropTypes.array.isRequired, + /** Options used to describe table */ + options: PropTypes.object.isRequired, + /** Data used to filter table against */ + filterList: PropTypes.array, + /** Callback to execute when row is clicked */ + onRowClick: PropTypes.func, + /** Table rows selected */ + selectedRows: PropTypes.object, + /** Callback to trigger table row select */ + selectRowUpdate: PropTypes.func, + /** Data used to search table against */ + searchText: PropTypes.string, + /** Toggle row expandable */ + toggleExpandRow: PropTypes.func, + /** Extend the style applied to components */ + classes: PropTypes.object, + }; + + static defaultProps = { + toggleExpandRow: () => {}, + }; + + buildRows() { + const { data, page, rowsPerPage, count } = this.props; + + if (this.props.options.serverSide) return data; + + let rows = []; + const totalPages = Math.floor(count / rowsPerPage); + const fromIndex = page === 0 ? 0 : page * rowsPerPage; + const toIndex = Math.min(count, (page + 1) * rowsPerPage); + + if (page > totalPages && totalPages !== 0) { + throw new Error( + 'Provided options.page of `' + + page + + '` is greater than the total available page length of `' + + totalPages + + '`', + ); + } + + for (let rowIndex = fromIndex; rowIndex < count && rowIndex < toIndex; rowIndex++) { + if (data[rowIndex] !== undefined) rows.push(data[rowIndex]); + } + + return rows.length ? rows : null; + } + + getRowIndex(index) { + const { page, rowsPerPage, options } = this.props; + + if (options.serverSide) { + return index; + } + + const startIndex = page === 0 ? 0 : page * rowsPerPage; + return startIndex + index; + } + + isRowSelected(dataIndex) { + const { selectedRows } = this.props; + return selectedRows.lookup && selectedRows.lookup[dataIndex] ? true : false; + } + + isRowExpanded(dataIndex) { + const { expandedRows } = this.props; + return expandedRows.lookup && expandedRows.lookup[dataIndex] ? true : false; + } + + handleRowSelect = data => { + this.props.selectRowUpdate('cell', data); + }; + + render() { + const { classes, columns, toggleExpandRow, options } = this.props; + const tableRows = this.buildRows(); + + return ( + + {tableRows ? ( + tableRows.map(({ data: row, dataIndex }, rowIndex) => ( + + + {options.selectableRows && ( + + )} + {row.map( + (column, columnIndex) => + columns[columnIndex].display === 'true' && ( + + {column} + + ), + )} + + {this.isRowExpanded(dataIndex) && options.renderExpandableRow(row, { rowIndex, dataIndex })} + + )) + ) : ( + + + + {options.textLabels.body.noMatch} + + + + )} + + ); + } +} + +export default withStyles(defaultBodyStyles, { name: 'MUIDataTableBody' })(TableBody); diff --git a/src/MUIDataTableBodyCell.js b/src/components/TableBodyCell.js similarity index 58% rename from src/MUIDataTableBodyCell.js rename to src/components/TableBodyCell.js index 31d709c20..6ee07a34b 100644 --- a/src/MUIDataTableBodyCell.js +++ b/src/components/TableBodyCell.js @@ -1,35 +1,35 @@ -import React from "react"; -import classNames from "classnames"; -import TableCell from "@material-ui/core/TableCell"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import classNames from 'classnames'; +import TableCell from '@material-ui/core/TableCell'; +import { withStyles } from '@material-ui/core/styles'; const defaultBodyCellStyles = theme => ({ root: {}, cellHide: { - display: "none", + display: 'none', }, cellStacked: { - [theme.breakpoints.down("sm")]: { - display: "inline-block", - backgroundColor: "#FFF", - fontSize: "16px", - height: "24px", - width: "calc(50% - 80px)", - whiteSpace: "nowrap", + [theme.breakpoints.down('sm')]: { + display: 'inline-block', + backgroundColor: '#FFF', + fontSize: '16px', + height: '24px', + width: 'calc(50% - 80px)', + whiteSpace: 'nowrap', }, }, responsiveStacked: { - [theme.breakpoints.down("sm")]: { - display: "inline-block", - fontSize: "16px", - width: "calc(50% - 80px)", - whiteSpace: "nowrap", - height: "24px", + [theme.breakpoints.down('sm')]: { + display: 'inline-block', + fontSize: '16px', + width: 'calc(50% - 80px)', + whiteSpace: 'nowrap', + height: '24px', }, }, }); -class MUIDataTableBodyCell extends React.Component { +class TableBodyCell extends React.Component { handleClick = () => { const { colIndex, options, children, dataIndex, rowIndex } = this.props; if (options.onCellClick) { @@ -57,7 +57,7 @@ class MUIDataTableBodyCell extends React.Component { { [classes.root]: true, [classes.cellHide]: true, - [classes.cellStacked]: options.responsive === "stacked", + [classes.cellStacked]: options.responsive === 'stacked', }, className, )}> @@ -69,7 +69,7 @@ class MUIDataTableBodyCell extends React.Component { className={classNames( { [classes.root]: true, - [classes.responsiveStacked]: options.responsive === "stacked", + [classes.responsiveStacked]: options.responsive === 'stacked', }, className, )} @@ -80,4 +80,4 @@ class MUIDataTableBodyCell extends React.Component { } } -export default withStyles(defaultBodyCellStyles, { name: "MUIDataTableBodyCell" })(MUIDataTableBodyCell); +export default withStyles(defaultBodyCellStyles, { name: 'MUIDataTableBodyCell' })(TableBodyCell); diff --git a/src/MUIDataTableBodyRow.js b/src/components/TableBodyRow.js similarity index 67% rename from src/MUIDataTableBodyRow.js rename to src/components/TableBodyRow.js index d9d88784e..fe3307d2f 100644 --- a/src/MUIDataTableBodyRow.js +++ b/src/components/TableBodyRow.js @@ -1,19 +1,19 @@ -import React from "react"; -import PropTypes from "prop-types"; -import classNames from "classnames"; -import TableRow from "@material-ui/core/TableRow"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import TableRow from '@material-ui/core/TableRow'; +import { withStyles } from '@material-ui/core/styles'; const defaultBodyRowStyles = theme => ({ root: {}, responsiveStacked: { - [theme.breakpoints.down("sm")]: { - border: "solid 2px rgba(0, 0, 0, 0.15)", + [theme.breakpoints.down('sm')]: { + border: 'solid 2px rgba(0, 0, 0, 0.15)', }, }, }); -class MUIDataTableBodyRow extends React.Component { +class TableBodyRow extends React.Component { static propTypes = { /** Options used to describe table */ options: PropTypes.object.isRequired, @@ -35,7 +35,7 @@ class MUIDataTableBodyRow extends React.Component { className={classNames( { [classes.root]: true, - [classes.responsiveStacked]: options.responsive === "stacked", + [classes.responsiveStacked]: options.responsive === 'stacked', }, className, )} @@ -47,4 +47,4 @@ class MUIDataTableBodyRow extends React.Component { } } -export default withStyles(defaultBodyRowStyles, { name: "MUIDataTableBodyRow" })(MUIDataTableBodyRow); +export default withStyles(defaultBodyRowStyles, { name: 'MUIDataTableBodyRow' })(TableBodyRow); diff --git a/src/MUIDataTableFilter.js b/src/components/TableFilter.js similarity index 70% rename from src/MUIDataTableFilter.js rename to src/components/TableFilter.js index 75c6980ec..3bf87c66a 100644 --- a/src/MUIDataTableFilter.js +++ b/src/components/TableFilter.js @@ -1,112 +1,112 @@ -import React from "react"; -import PropTypes from "prop-types"; -import classNames from "classnames"; -import Typography from "@material-ui/core/Typography"; -import FormControl from "@material-ui/core/FormControl"; -import FormGroup from "@material-ui/core/FormGroup"; -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import InputLabel from "@material-ui/core/InputLabel"; -import Input from "@material-ui/core/Input"; -import MenuItem from "@material-ui/core/MenuItem"; -import Select from "@material-ui/core/Select"; -import Checkbox from "@material-ui/core/Checkbox"; -import ListItemText from "@material-ui/core/ListItemText"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Typography from '@material-ui/core/Typography'; +import FormControl from '@material-ui/core/FormControl'; +import FormGroup from '@material-ui/core/FormGroup'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import InputLabel from '@material-ui/core/InputLabel'; +import Input from '@material-ui/core/Input'; +import MenuItem from '@material-ui/core/MenuItem'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; +import ListItemText from '@material-ui/core/ListItemText'; +import { withStyles } from '@material-ui/core/styles'; export const defaultFilterStyles = { root: { - padding: "16px 24px 16px 24px", - fontFamily: "Roboto", + padding: '16px 24px 16px 24px', + fontFamily: 'Roboto', }, header: { - flex: "0 0 auto", - marginBottom: "16px", - width: "100%", - display: "flex", - justifyContent: "space-between", + flex: '0 0 auto', + marginBottom: '16px', + width: '100%', + display: 'flex', + justifyContent: 'space-between', }, title: { - display: "inline-block", - marginLeft: "7px", - color: "#424242", - fontSize: "14px", + display: 'inline-block', + marginLeft: '7px', + color: '#424242', + fontSize: '14px', fontWeight: 500, }, noMargin: { - marginLeft: "0px", + marginLeft: '0px', }, reset: { - alignSelf: "left", + alignSelf: 'left', }, resetLink: { - color: "#027cb5", - backgroundColor: "#FFF", - display: "inline-block", - marginLeft: "24px", - fontSize: "12px", - cursor: "pointer", - border: "none", - "&:hover": { - color: "#FF0000", + color: '#027cb5', + backgroundColor: '#FFF', + display: 'inline-block', + marginLeft: '24px', + fontSize: '12px', + cursor: 'pointer', + border: 'none', + '&:hover': { + color: '#FF0000', }, }, filtersSelected: { - alignSelf: "right", + alignSelf: 'right', }, /* checkbox */ checkboxList: { - flex: "1 1 100%", - display: "inline-flex", - marginRight: "24px", + flex: '1 1 100%', + display: 'inline-flex', + marginRight: '24px', }, checkboxListTitle: { - marginLeft: "7px", - marginBottom: "8px", - fontSize: "14px", - color: "#424242", - textAlign: "left", + marginLeft: '7px', + marginBottom: '8px', + fontSize: '14px', + color: '#424242', + textAlign: 'left', fontWeight: 500, }, checkboxFormGroup: { - marginTop: "8px", + marginTop: '8px', }, checkboxFormControl: { - margin: "0px", + margin: '0px', }, checkboxFormControlLabel: { - fontSize: "15px", - marginLeft: "8px", - color: "#4a4a4a", + fontSize: '15px', + marginLeft: '8px', + color: '#4a4a4a', }, checkboxIcon: { //color: "#027cb5", - width: "32px", - height: "32px", + width: '32px', + height: '32px', }, checkbox: { - "&$checked": { - color: "#027cB5", + '&$checked': { + color: '#027cB5', }, }, checked: {}, /* selects */ selectRoot: { - display: "flex", - marginTop: "16px", - flexDirection: "row", - flexWrap: "wrap", - width: "100%", - height: "80%", - justifyContent: "space-between", + display: 'flex', + marginTop: '16px', + flexDirection: 'row', + flexWrap: 'wrap', + width: '100%', + height: '80%', + justifyContent: 'space-between', }, selectFormControl: { - flex: "1 1 calc(50% - 24px)", - marginRight: "24px", - marginBottom: "24px", + flex: '1 1 calc(50% - 24px)', + marginRight: '24px', + marginBottom: '24px', }, }; -class MUIDataTableFilter extends React.Component { +class TableFilter extends React.Component { static propTypes = { /** Data used to populate filter dropdown/checkbox */ filterData: PropTypes.array.isRequired, @@ -123,16 +123,16 @@ class MUIDataTableFilter extends React.Component { }; handleCheckboxChange = (index, column) => { - this.props.onFilterUpdate(index, column, "checkbox"); + this.props.onFilterUpdate(index, column, 'checkbox'); }; handleDropdownChange = (event, index) => { - const value = event.target.value === "All" ? "" : event.target.value; - this.props.onFilterUpdate(index, value, "dropdown"); + const value = event.target.value === 'All' ? '' : event.target.value; + this.props.onFilterUpdate(index, value, 'dropdown'); }; handleMultiselectChange = (index, column) => { - this.props.onFilterUpdate(index, column, "multiselect"); + this.props.onFilterUpdate(index, column, 'multiselect'); }; renderCheckbox(columns) { @@ -161,7 +161,7 @@ class MUIDataTableFilter extends React.Component { root: classes.checkbox, checked: classes.checked, }} - value={filterColumn !== null ? filterColumn.toString() : ""} + value={filterColumn !== null ? filterColumn.toString() : ''} /> } label={filterColumn} @@ -195,7 +195,7 @@ class MUIDataTableFilter extends React.Component { {filterData[index].map((filterColumn, filterIndex) => ( - {filterColumn !== null ? filterColumn.toString() : ""} + {filterColumn !== null ? filterColumn.toString() : ''} ))} @@ -220,7 +220,7 @@ class MUIDataTableFilter extends React.Component { }> @@ -260,7 +260,7 @@ class MUIDataTableFilter extends React.Component { variant="caption" className={classNames({ [classes.title]: true, - [classes.noMargin]: options.filterType !== "checkbox" ? true : false, + [classes.noMargin]: options.filterType !== 'checkbox' ? true : false, })}> {textLabels.title} @@ -270,9 +270,9 @@ class MUIDataTableFilter extends React.Component {
- {options.filterType === "checkbox" + {options.filterType === 'checkbox' ? this.renderCheckbox(columns) - : options.filterType === "multiselect" + : options.filterType === 'multiselect' ? this.renderMultiselect(columns) : this.renderSelect(columns)} @@ -280,4 +280,4 @@ class MUIDataTableFilter extends React.Component { } } -export default withStyles(defaultFilterStyles, { name: "MUIDataTableFilter" })(MUIDataTableFilter); +export default withStyles(defaultFilterStyles, { name: 'MUIDataTableFilter' })(TableFilter); diff --git a/src/MUIDataTableFilterList.js b/src/components/TableFilterList.js similarity index 57% rename from src/MUIDataTableFilterList.js rename to src/components/TableFilterList.js index 8ef1bd9d3..fbb65accf 100644 --- a/src/MUIDataTableFilterList.js +++ b/src/components/TableFilterList.js @@ -1,21 +1,21 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Chip from "@material-ui/core/Chip"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Chip from '@material-ui/core/Chip'; +import { withStyles } from '@material-ui/core/styles'; const defaultFilterListStyles = { root: { - display: "flex", - justifyContent: "left", - flexWrap: "wrap", - margin: "0px 16px 0px 16px", + display: 'flex', + justifyContent: 'left', + flexWrap: 'wrap', + margin: '0px 16px 0px 16px', }, chip: { - margin: "8px 8px 0px 0px", + margin: '8px 8px 0px 0px', }, }; -class MUIDataTableFilterList extends React.Component { +class TableFilterList extends React.Component { static propTypes = { /** Data used to filter table against */ filterList: PropTypes.array.isRequired, @@ -35,7 +35,7 @@ class MUIDataTableFilterList extends React.Component { )), @@ -45,4 +45,4 @@ class MUIDataTableFilterList extends React.Component { } } -export default withStyles(defaultFilterListStyles, { name: "MUIDataTableFilterList" })(MUIDataTableFilterList); +export default withStyles(defaultFilterListStyles, { name: 'MUIDataTableFilterList' })(TableFilterList); diff --git a/src/components/TableFooter.js b/src/components/TableFooter.js new file mode 100644 index 000000000..c5c33a7f1 --- /dev/null +++ b/src/components/TableFooter.js @@ -0,0 +1,36 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import MuiTable from '@material-ui/core/Table'; +import TableHead from './TableHead'; +import TablePagination from './TablePagination'; +import { withStyles } from '@material-ui/core/styles'; + +export const defaultFooterStyles = {}; + +class TableFooter extends React.Component { + static propTypes = {}; + + render() { + const { options, rowCount, page, rowsPerPage, changeRowsPerPage, changePage } = this.props; + + return ( + + {options.customFooter + ? options.customFooter(rowCount, page, rowsPerPage, changeRowsPerPage, changePage) + : options.pagination && ( + + )} + + ); + } +} + +export default TableFooter; diff --git a/src/MUIDataTableHead.js b/src/components/TableHead.js similarity index 62% rename from src/MUIDataTableHead.js rename to src/components/TableHead.js index f4b2bdbb8..4da159e42 100644 --- a/src/MUIDataTableHead.js +++ b/src/components/TableHead.js @@ -1,22 +1,22 @@ -import React from "react"; -import { findDOMNode } from "react-dom"; -import classNames from "classnames"; -import TableHead from "@material-ui/core/TableHead"; -import MUIDataTableHeadRow from "./MUIDataTableHeadRow"; -import MUIDataTableHeadCell from "./MUIDataTableHeadCell"; -import MUIDataTableSelectCell from "./MUIDataTableSelectCell"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import { findDOMNode } from 'react-dom'; +import classNames from 'classnames'; +import MuiTableHead from '@material-ui/core/TableHead'; +import TableHeadRow from './TableHeadRow'; +import TableHeadCell from './TableHeadCell'; +import TableSelectCell from './TableSelectCell'; +import { withStyles } from '@material-ui/core/styles'; const defaultHeadStyles = theme => ({ main: {}, responsiveStacked: { - [theme.breakpoints.down("sm")]: { - display: "none", + [theme.breakpoints.down('sm')]: { + display: 'none', }, }, }); -class MUIDataTableHead extends React.Component { +class TableHead extends React.Component { componentDidMount() { this.props.handleHeadUpdateRef(this.handleUpdateCheck); } @@ -26,7 +26,7 @@ class MUIDataTableHead extends React.Component { }; handleRowSelect = () => { - this.props.selectRowUpdate("head", null); + this.props.selectRowUpdate('head', null); }; render() { @@ -37,29 +37,30 @@ class MUIDataTableHead extends React.Component { const isChecked = numSelected === count ? true : false; return ( - - + + {options.selectableRows && ( - setCellRef(0, findDOMNode(el))} onChange={this.handleRowSelect.bind(null)} indeterminate={isDeterminate} checked={isChecked} isHeaderCell={true} + isExpandable={options.expandableRows} fixedHeader={options.fixedHeader} /> )} {columns.map( (column, index) => - column.display === "true" && + column.display === 'true' && (column.customHeadRender ? ( column.customHeadRender({ index, ...column }, this.handleToggleColumn) ) : ( - setCellRef(index + 1, findDOMNode(el))} sort={column.sort} sortDirection={column.sortDirection} @@ -67,13 +68,13 @@ class MUIDataTableHead extends React.Component { hint={column.hint} options={options}> {column.name} - + )), )} - - + + ); } } -export default withStyles(defaultHeadStyles, { name: "MUIDataTableHead" })(MUIDataTableHead); +export default withStyles(defaultHeadStyles, { name: 'MUIDataTableHead' })(TableHead); diff --git a/src/MUIDataTableHeadCell.js b/src/components/TableHeadCell.js similarity index 69% rename from src/MUIDataTableHeadCell.js rename to src/components/TableHeadCell.js index 4f25275dd..832339b63 100644 --- a/src/MUIDataTableHeadCell.js +++ b/src/components/TableHeadCell.js @@ -1,50 +1,50 @@ -import React from "react"; -import PropTypes from "prop-types"; -import classNames from "classnames"; -import TableCell from "@material-ui/core/TableCell"; -import TableSortLabel from "@material-ui/core/TableSortLabel"; -import Tooltip from "@material-ui/core/Tooltip"; -import { withStyles } from "@material-ui/core/styles"; -import HelpIcon from "@material-ui/icons/Help"; +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import TableCell from '@material-ui/core/TableCell'; +import TableSortLabel from '@material-ui/core/TableSortLabel'; +import Tooltip from '@material-ui/core/Tooltip'; +import { withStyles } from '@material-ui/core/styles'; +import HelpIcon from '@material-ui/icons/Help'; const defaultHeadCellStyles = { root: {}, fixedHeader: { - position: "sticky", - top: "0px", - left: "0px", + position: 'sticky', + top: '0px', + left: '0px', zIndex: 100, - backgroundColor: "#FFF", + backgroundColor: '#FFF', }, tooltip: { - cursor: "pointer", + cursor: 'pointer', }, mypopper: { - "&[data-x-out-of-boundaries]": { - display: "none", + '&[data-x-out-of-boundaries]': { + display: 'none', }, }, data: { - display: "inline-block", + display: 'inline-block', }, sortAction: { - display: "inline-block", - verticalAlign: "top", - cursor: "pointer", - paddingLeft: "4px", - height: "10px", + display: 'inline-block', + verticalAlign: 'top', + cursor: 'pointer', + paddingLeft: '4px', + height: '10px', }, sortActive: { - color: "rgba(0, 0, 0, 0.87)", + color: 'rgba(0, 0, 0, 0.87)', }, toolButton: { - height: "10px", - outline: "none", - cursor: "pointer", + height: '10px', + outline: 'none', + cursor: 'pointer', }, }; -class MUIDataTableHeadCell extends React.Component { +class TableHeadCell extends React.Component { static propTypes = { /** Extend the style applied to components */ classes: PropTypes.object, @@ -79,11 +79,11 @@ class MUIDataTableHeadCell extends React.Component { }); return ( - + {options.sort && sort ? ( - - + + `${from}-${to} ${textLabels.displayRows} ${count}`} backIconButtonProps={{ - "aria-label": textLabels.previous, + 'aria-label': textLabels.previous, }} nextIconButtonProps={{ - "aria-label": textLabels.next, + 'aria-label': textLabels.next, }} rowsPerPageOptions={options.rowsPerPageOptions} onChangePage={this.handlePageChange} onChangeRowsPerPage={this.handleRowChange} /> - - + + ); } } -export default withStyles(defaultPaginationStyles, { name: "MUIDataTablePagination" })(MUIDataTablePagination); +export default withStyles(defaultPaginationStyles, { name: 'MUIDataTablePagination' })(TablePagination); diff --git a/src/MUIDataTableResize.js b/src/components/TableResize.js similarity index 78% rename from src/MUIDataTableResize.js rename to src/components/TableResize.js index ae1552f4a..75e3bc861 100644 --- a/src/MUIDataTableResize.js +++ b/src/components/TableResize.js @@ -1,24 +1,24 @@ -import React from "react"; -import PropTypes from "prop-types"; -import classNames from "classnames"; -import { findDOMNode } from "react-dom"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { findDOMNode } from 'react-dom'; +import { withStyles } from '@material-ui/core/styles'; const defaultResizeStyles = { root: { - position: "absolute", + position: 'absolute', }, resizer: { - position: "absolute", - width: "1px", - height: "100%", - left: "100px", - cursor: "ew-resize", - border: "0.1px solid rgba(224, 224, 224, 1)", + position: 'absolute', + width: '1px', + height: '100%', + left: '100px', + cursor: 'ew-resize', + border: '0.1px solid rgba(224, 224, 224, 1)', }, }; -class MUIDataTableResize extends React.Component { +class TableResize extends React.Component { static propTypes = { /** Extend the style applied to components */ classes: PropTypes.object, @@ -27,8 +27,8 @@ class MUIDataTableResize extends React.Component { state = { resizeCoords: {}, startPosition: 0, - tableWidth: "100%", - tableHeight: "100%", + tableWidth: '100%', + tableHeight: '100%', }; handleReize = () => { @@ -41,11 +41,11 @@ class MUIDataTableResize extends React.Component { componentDidMount() { this.windowWidth = null; this.props.setResizeable(this.setCellRefs); - window.addEventListener("resize", this.handleReize, false); + window.addEventListener('resize', this.handleReize, false); } componentWillUnmount() { - window.removeEventListener("resize", this.handleReize, false); + window.removeEventListener('resize', this.handleReize, false); } setCellRefs = (cellsRef, tableRef) => { @@ -83,7 +83,7 @@ class MUIDataTableResize extends React.Component { lastPosition = item.left; const thCell = this.cellsRef[key]; - thCell.style.width = newWidth + "%"; + thCell.style.width = newWidth + '%'; }); }; @@ -122,8 +122,8 @@ class MUIDataTableResize extends React.Component { onMouseMove={this.onResizeMove.bind(null, key)} onMouseUp={this.onResizeEnd.bind(null, key)} style={{ - width: isResize && id == key ? tableWidth : "auto", - position: "absolute", + width: isResize && id == key ? tableWidth : 'auto', + position: 'absolute', height: tableHeight, zIndex: 1000, }}> @@ -141,4 +141,4 @@ class MUIDataTableResize extends React.Component { } } -export default withStyles(defaultResizeStyles, { name: "MUIDataTableResize" })(MUIDataTableResize); +export default withStyles(defaultResizeStyles, { name: 'MUIDataTableResize' })(TableResize); diff --git a/src/MUIDataTableSearch.js b/src/components/TableSearch.js similarity index 58% rename from src/MUIDataTableSearch.js rename to src/components/TableSearch.js index 771a99bc4..72e461fea 100644 --- a/src/MUIDataTableSearch.js +++ b/src/components/TableSearch.js @@ -1,31 +1,31 @@ -import React from "react"; -import Grow from "@material-ui/core/Grow"; -import TextField from "@material-ui/core/TextField"; -import SearchIcon from "@material-ui/icons/Search"; -import IconButton from "@material-ui/core/IconButton"; -import ClearIcon from "@material-ui/icons/Clear"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import Grow from '@material-ui/core/Grow'; +import TextField from '@material-ui/core/TextField'; +import SearchIcon from '@material-ui/icons/Search'; +import IconButton from '@material-ui/core/IconButton'; +import ClearIcon from '@material-ui/icons/Clear'; +import { withStyles } from '@material-ui/core/styles'; const defaultSearchStyles = { main: { - display: "flex", - flex: "1 0 auto", + display: 'flex', + flex: '1 0 auto', }, searchIcon: { - marginTop: "10px", - marginRight: "8px", + marginTop: '10px', + marginRight: '8px', }, searchText: { - flex: "0.8 0", + flex: '0.8 0', }, clearIcon: { - "&:hover": { - color: "#FF0000", + '&:hover': { + color: '#FF0000', }, }, }; -class MUIDataTableSearch extends React.Component { +class TableSearch extends React.Component { handleTextChange = event => { const { onSearchChange } = this.props.options; @@ -37,11 +37,11 @@ class MUIDataTableSearch extends React.Component { }; componentDidMount() { - document.addEventListener("keydown", this.onKeyDown, false); + document.addEventListener('keydown', this.onKeyDown, false); } componentWillUnmount() { - document.removeEventListener("keydown", this.onKeyDown, false); + document.removeEventListener('keydown', this.onKeyDown, false); } onKeyDown = event => { @@ -61,7 +61,7 @@ class MUIDataTableSearch extends React.Component { className={classes.searchText} autoFocus={true} InputProps={{ - "aria-label": options.textLabels.toolbar.search, + 'aria-label': options.textLabels.toolbar.search, }} onChange={this.handleTextChange} fullWidth={true} @@ -76,4 +76,4 @@ class MUIDataTableSearch extends React.Component { } } -export default withStyles(defaultSearchStyles, { name: "MUIDataTableSearch" })(MUIDataTableSearch); +export default withStyles(defaultSearchStyles, { name: 'MUIDataTableSearch' })(TableSearch); diff --git a/src/components/TableSelectCell.js b/src/components/TableSelectCell.js new file mode 100644 index 000000000..f64d3f1e5 --- /dev/null +++ b/src/components/TableSelectCell.js @@ -0,0 +1,95 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import Checkbox from '@material-ui/core/Checkbox'; +import TableCell from '@material-ui/core/TableCell'; +import { withStyles } from '@material-ui/core/styles'; +import KeyboardArrowRight from '@material-ui/icons/KeyboardArrowRight'; + +const defaultSelectCellStyles = theme => ({ + root: { + [theme.breakpoints.down('sm')]: { + display: 'none', + }, + }, + fixedHeader: { + position: 'sticky', + top: '0px', + left: '0px', + zIndex: 100, + }, + icon: { + cursor: 'pointer', + transition: 'transform 0.25s', + }, + expanded: { + transform: 'rotate(90deg)', + }, + hide: { + visibility: 'hidden', + }, + headerCell: { + zIndex: 110, + backgroundColor: '#FFF', + }, + checkboxRoot: { + '&$checked': { + color: '#027cb5', + }, + }, + checked: {}, + disabled: {}, +}); + +class TableSelectCell extends React.Component { + static propTypes = { + /** Select cell checked on/off */ + checked: PropTypes.bool.isRequired, + /** Select cell part of fixed header */ + fixedHeader: PropTypes.bool.isRequired, + /** Callback to trigger cell update */ + onChange: PropTypes.func, + /** Extend the style applied to components */ + classes: PropTypes.object, + }; + + static defaultProps = { + isHeaderCell: false, + isExpandable: false, + isRowExpanded: false, + }; + + render() { + const { classes, fixedHeader, isHeaderCell, isExpandable, isRowExpanded, onExpand, ...otherProps } = this.props; + + const cellClass = classNames({ + [classes.root]: true, + [classes.fixedHeader]: fixedHeader, + [classes.headerCell]: isHeaderCell, + }); + + const iconClass = classNames({ + [classes.icon]: true, + [classes.hide]: isHeaderCell, + [classes.expanded]: isRowExpanded, + }); + + return ( + +
+ {isExpandable && } + +
+
+ ); + } +} + +export default withStyles(defaultSelectCellStyles, { name: 'MUIDataTableSelectCell' })(TableSelectCell); diff --git a/src/components/TableToolbar.js b/src/components/TableToolbar.js new file mode 100644 index 000000000..c229e26b0 --- /dev/null +++ b/src/components/TableToolbar.js @@ -0,0 +1,240 @@ +import React from 'react'; +import Typography from '@material-ui/core/Typography'; +import Toolbar from '@material-ui/core/Toolbar'; +import Tooltip from '@material-ui/core/Tooltip'; +import IconButton from '@material-ui/core/IconButton'; +import Popover from './Popover'; +import TableFilter from './TableFilter'; +import TableViewCol from './TableViewCol'; +import TableSearch from './TableSearch'; +import SearchIcon from '@material-ui/icons/Search'; +import DownloadIcon from '@material-ui/icons/CloudDownload'; +import PrintIcon from '@material-ui/icons/Print'; +import ViewColumnIcon from '@material-ui/icons/ViewColumn'; +import FilterIcon from '@material-ui/icons/FilterList'; +import ReactToPrint from 'react-to-print'; +import styled from '../styled'; +import { createCSVDownload } from '../utils'; + +export const defaultToolbarStyles = (theme, props) => ({ + root: {}, + left: { + flex: '1 1 55%', + }, + actions: { + flex: '0 0 45%', + textAlign: 'right', + }, + titleRoot: {}, + titleText: {}, + icon: { + '&:hover': { + color: '#307BB0', + }, + }, + iconActive: { + color: '#307BB0', + }, + searchIcon: { + display: 'inline-flex', + marginTop: '10px', + marginRight: '8px', + }, + ...(props.options.responsive ? { ...responsiveToolbarStyles(theme) } : {}), +}); + +export const responsiveToolbarStyles = theme => ({ + [theme.breakpoints.down('sm')]: { + titleRoot: {}, + titleText: { + fontSize: '16px', + }, + spacer: { + display: 'none', + }, + left: { + // flex: "1 1 40%", + padding: '8px 0px', + }, + actions: { + // flex: "1 1 60%", + textAlign: 'right', + }, + }, + [theme.breakpoints.down('xs')]: { + root: { + display: 'block', + }, + left: { + padding: '8px 0px 0px 0px', + }, + titleText: { + textAlign: 'center', + }, + actions: { + textAlign: 'center', + }, + }, + '@media screen and (max-width: 480px)': {}, +}); + +class TableToolbar extends React.Component { + state = { + iconActive: null, + showSearch: false, + }; + + handleCSVDownload = () => { + const { data, columns, options } = this.props; + createCSVDownload(columns, data, options); + }; + + setActiveIcon = iconName => { + this.setState(() => ({ + iconActive: iconName, + showSearch: iconName === 'search' ? this.showSearch() : false, + })); + }; + + getActiveIcon = (styles, iconName) => { + return this.state.iconActive !== iconName ? styles.icon : styles.iconActive; + }; + + showSearch = () => { + !!this.props.options.onSearchOpen && this.props.options.onSearchOpen(); + this.props.setTableAction('onSearchOpen'); + return true; + }; + + hideSearch = () => { + const { onSearchClose } = this.props.options; + + if (onSearchClose) onSearchClose(); + this.props.searchTextUpdate(null); + + this.setState(() => ({ + iconActive: null, + showSearch: false, + })); + + this.searchButton.focus(); + }; + + render() { + const { + data, + options, + classes, + columns, + filterData, + filterList, + filterUpdate, + resetFilters, + searchTextUpdate, + toggleViewColumn, + title, + tableRef, + } = this.props; + + const { search, downloadCsv, print, viewColumns, filterTable } = options.textLabels.toolbar; + const { showSearch } = this.state; + + return ( + +
+ {showSearch === true ? ( + + ) : ( +
+ + {title} + +
+ )} +
+
+ {options.search && ( + + (this.searchButton = el)} + classes={{ root: this.getActiveIcon(classes, 'search') }} + onClick={this.setActiveIcon.bind(null, 'search')}> + + + + )} + {options.download && ( + + + + + + )} + {options.print && ( + + + ( + + + + )} + content={() => this.props.tableRef()} + /> + + + )} + {options.viewColumns && ( + + + + + + } + content={ + + } + /> + )} + {options.filter && ( + + + + + + } + content={ + + } + /> + )} + {options.customToolbar && options.customToolbar()} +
+
+ ); + } +} + +export default styled(TableToolbar)(defaultToolbarStyles, { name: 'MUIDataTableToolbar' }); diff --git a/src/MUIDataTableToolbarSelect.js b/src/components/TableToolbarSelect.js similarity index 61% rename from src/MUIDataTableToolbarSelect.js rename to src/components/TableToolbarSelect.js index 7db8d63ae..536d260c4 100644 --- a/src/MUIDataTableToolbarSelect.js +++ b/src/components/TableToolbarSelect.js @@ -1,39 +1,41 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Paper from "@material-ui/core/Paper"; -import IconButton from "@material-ui/core/IconButton"; -import Tooltip from "@material-ui/core/Tooltip"; -import Typography from "@material-ui/core/Typography"; -import DeleteIcon from "@material-ui/icons/Delete"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Paper from '@material-ui/core/Paper'; +import IconButton from '@material-ui/core/IconButton'; +import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { withStyles } from '@material-ui/core/styles'; const defaultToolbarSelectStyles = { root: { - backgroundColor: "#f7f7f7", - flex: "1 1 100%", - display: "flex", - height: "64px", - justifyContent: "space-between", + backgroundColor: '#f7f7f7', + flex: '1 1 100%', + display: 'flex', + height: '64px', + position: 'relative', + zIndex: 120, + justifyContent: 'space-between', }, title: { - paddingLeft: "26px", - top: "50%", - position: "relative", - transform: "translateY(-50%)", + paddingLeft: '26px', + top: '50%', + position: 'relative', + transform: 'translateY(-50%)', }, iconButton: { - marginRight: "24px", - top: "50%", - display: "block", - position: "relative", - transform: "translateY(-50%)", + marginRight: '24px', + top: '50%', + display: 'block', + position: 'relative', + transform: 'translateY(-50%)', }, deleteIcon: { - color: "#000", + color: '#000', }, }; -class MUIDataTableToolbarSelect extends React.Component { +class TableToolbarSelect extends React.Component { static propTypes = { /** Options used to describe table */ options: PropTypes.object.isRequired, @@ -53,11 +55,11 @@ class MUIDataTableToolbarSelect extends React.Component { throw new TypeError(`"selectedRows" must be an "array", but it's "${typeof selectedRows}"`); } - if (selectedRows.some(row => typeof row !== "number")) { + if (selectedRows.some(row => typeof row !== 'number')) { throw new TypeError(`Array "selectedRows" must contain only numbers`); } - this.props.selectRowUpdate("custom", selectedRows); + this.props.selectRowUpdate('custom', selectedRows); }; render() { @@ -85,4 +87,4 @@ class MUIDataTableToolbarSelect extends React.Component { } } -export default withStyles(defaultToolbarSelectStyles, { name: "MUIDataTableToolbarSelect" })(MUIDataTableToolbarSelect); +export default withStyles(defaultToolbarSelectStyles, { name: 'MUIDataTableToolbarSelect' })(TableToolbarSelect); diff --git a/src/MUIDataTableViewCol.js b/src/components/TableViewCol.js similarity index 63% rename from src/MUIDataTableViewCol.js rename to src/components/TableViewCol.js index 1b572679d..5d7233965 100644 --- a/src/MUIDataTableViewCol.js +++ b/src/components/TableViewCol.js @@ -1,47 +1,47 @@ -import React from "react"; -import PropTypes from "prop-types"; -import Checkbox from "@material-ui/core/Checkbox"; -import Typography from "@material-ui/core/Typography"; -import FormControl from "@material-ui/core/FormControl"; -import FormGroup from "@material-ui/core/FormGroup"; -import FormControlLabel from "@material-ui/core/FormControlLabel"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import Checkbox from '@material-ui/core/Checkbox'; +import Typography from '@material-ui/core/Typography'; +import FormControl from '@material-ui/core/FormControl'; +import FormGroup from '@material-ui/core/FormGroup'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import { withStyles } from '@material-ui/core/styles'; export const defaultViewColStyles = { root: { - padding: "16px 24px 16px 24px", - fontFamily: "Roboto", + padding: '16px 24px 16px 24px', + fontFamily: 'Roboto', }, title: { - marginLeft: "-7px", - fontSize: "14px", - color: "#424242", - textAlign: "left", + marginLeft: '-7px', + fontSize: '14px', + color: '#424242', + textAlign: 'left', fontWeight: 500, }, formGroup: { - marginTop: "8px", + marginTop: '8px', }, formControl: {}, checkbox: { - padding: "0px", - width: "32px", - height: "32px", + padding: '0px', + width: '32px', + height: '32px', }, checkboxRoot: { - "&$checked": { - color: "#027cb5", + '&$checked': { + color: '#027cb5', }, }, checked: {}, label: { - fontSize: "15px", - marginLeft: "8px", - color: "#4a4a4a", + fontSize: '15px', + marginLeft: '8px', + color: '#4a4a4a', }, }; -class MUIDataTableViewCol extends React.Component { +class TableViewCol extends React.Component { static propTypes = { /** Columns used to describe table */ columns: PropTypes.array.isRequired, @@ -62,14 +62,14 @@ class MUIDataTableViewCol extends React.Component { const textLabels = options.textLabels.viewColumns; return ( - + {textLabels.title} {columns.map((column, index) => { return ( - column.display !== "excluded" && ( + column.display !== 'excluded' && ( } @@ -99,4 +99,4 @@ class MUIDataTableViewCol extends React.Component { } } -export default withStyles(defaultViewColStyles, { name: "MUIDataTableViewCol" })(MUIDataTableViewCol); +export default withStyles(defaultViewColStyles, { name: 'MUIDataTableViewCol' })(TableViewCol); diff --git a/src/index.js b/src/index.js index d061642b5..b1b6d287d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,3 @@ -import MUIDataTable from "./MUIDataTable"; +import MUIDataTable from './MUIDataTable'; export default MUIDataTable; diff --git a/src/styled.js b/src/styled.js index 14c59fa96..543db2f50 100644 --- a/src/styled.js +++ b/src/styled.js @@ -1,7 +1,7 @@ -import React from "react"; -import PropTypes from "prop-types"; -import merge from "lodash.merge"; -import { withStyles } from "@material-ui/core/styles"; +import React from 'react'; +import PropTypes from 'prop-types'; +import merge from 'lodash.merge'; +import { withStyles } from '@material-ui/core/styles'; /* * Material-UI does not yet support ability to grab props within style() @@ -11,7 +11,7 @@ import { withStyles } from "@material-ui/core/styles"; */ const styles = (theme, props, style) => { - return typeof style === "function" ? style(theme, props) : style; + return typeof style === 'function' ? style(theme, props) : style; }; class StyledComponent extends React.Component { @@ -21,7 +21,7 @@ class StyledComponent extends React.Component { }; render() { - const { classes, className = "", WrappedComponent, ...passThroughProps } = this.props; + const { classes, className = '', WrappedComponent, ...passThroughProps } = this.props; return ; } diff --git a/src/textLabels.js b/src/textLabels.js index 8d342a7bf..a2df615f0 100644 --- a/src/textLabels.js +++ b/src/textLabels.js @@ -3,35 +3,35 @@ */ const textLabels = { body: { - noMatch: "Sorry, no matching records found", - toolTip: "Sort", + noMatch: 'Sorry, no matching records found', + toolTip: 'Sort', }, pagination: { - next: "Next Page", - previous: "Previous Page", - rowsPerPage: "Rows per page:", - displayRows: "of", + next: 'Next Page', + previous: 'Previous Page', + rowsPerPage: 'Rows per page:', + displayRows: 'of', }, toolbar: { - search: "Search", - downloadCsv: "Download CSV", - print: "Print", - viewColumns: "View Columns", - filterTable: "Filter Table", + search: 'Search', + downloadCsv: 'Download CSV', + print: 'Print', + viewColumns: 'View Columns', + filterTable: 'Filter Table', }, filter: { - all: "All", - title: "FILTERS", - reset: "RESET", + all: 'All', + title: 'FILTERS', + reset: 'RESET', }, viewColumns: { - title: "Show Columns", - titleAria: "Show/Hide Table Columns", + title: 'Show Columns', + titleAria: 'Show/Hide Table Columns', }, selectedRows: { - text: "row(s) selected", - delete: "Delete", - deleteAria: "Delete Selected Rows", + text: 'row(s) selected', + delete: 'Delete', + deleteAria: 'Delete Selected Rows', }, }; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 000000000..54aa8be20 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,71 @@ +function buildMap(rows) { + return rows.reduce((accum, { dataIndex }) => { + accum[dataIndex] = true; + return accum; + }, {}); +} + +function getCollatorComparator() { + if (!!Intl) { + const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); + return collator.compare; + } + + const fallbackComparator = (a, b) => a.localeCompare(b); + return fallbackComparator; +} + +function sortCompare(order) { + return (a, b) => { + if (a.data === null) a.data = ''; + if (b.data === null) b.data = ''; + return ( + (typeof a.data.localeCompare === 'function' ? a.data.localeCompare(b.data) : a.data - b.data) * + (order === 'asc' ? -1 : 1) + ); + }; +} + +function createCSVDownload(columns, data, options) { + const CSVHead = + columns + .reduce( + (soFar, column) => + column.download ? soFar + '"' + column.name + '"' + options.downloadOptions.separator : soFar, + '', + ) + .slice(0, -1) + '\r\n'; + + const CSVBody = data + .reduce( + (soFar, row) => + soFar + + '"' + + row.data.filter((field, index) => columns[index].download).join('"' + options.downloadOptions.separator + '"') + + '"\r\n', + [], + ) + .trim(); + + const csv = `${CSVHead}${CSVBody}`; + const blob = new Blob([csv], { type: 'text/csv' }); + + /* taken from react-csv */ + if (navigator && navigator.msSaveOrOpenBlob) { + navigator.msSaveOrOpenBlob(blob, options.downloadOptions.filename); + } else { + const dataURI = `data:text/csv;charset=utf-8,${csv}`; + + const URL = window.URL || window.webkitURL; + const downloadURI = typeof URL.createObjectURL === 'undefined' ? dataURI : URL.createObjectURL(blob); + + let link = document.createElement('a'); + link.setAttribute('href', downloadURI); + link.setAttribute('download', options.downloadOptions.filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } +} + +export { buildMap, getCollatorComparator, sortCompare, createCSVDownload }; diff --git a/test/MUIDataTable.test.js b/test/MUIDataTable.test.js index 2e8798d9a..377c988fb 100644 --- a/test/MUIDataTable.test.js +++ b/test/MUIDataTable.test.js @@ -1,15 +1,16 @@ -import React from "react"; -import { spy } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect } from "chai"; -import MUIDataTable from "../src/MUIDataTable"; -import MUIDataTableFilterList from "../src/MUIDataTableFilterList"; -import MUIDataTablePagination from "../src/MUIDataTablePagination"; -import textLabels from "../src/textLabels"; -import Chip from "@material-ui/core/Chip"; -import Cities from "../examples/component/cities"; - -describe("", function() { +import React from 'react'; +import { spy } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect } from 'chai'; +import MUIDataTable from '../src/MUIDataTable'; +import TableFilterList from '../src/components/TableFilterList'; +import TablePagination from '../src/components/TablePagination'; +import textLabels from '../src/textLabels'; +import Chip from '@material-ui/core/Chip'; +import Cities from '../examples/component/cities'; +import { getCollatorComparator } from '../src/utils'; + +describe('', function() { let data; let displayData; let columns; @@ -17,99 +18,99 @@ describe("", function() { let renderCities = (value, tableMeta, updateValueFn) => ( updateValueFn(event)} /> ); - let renderName = value => value.split(" ")[1] + ", " + value.split(" ")[0]; + let renderName = value => value.split(' ')[1] + ', ' + value.split(' ')[0]; before(() => { columns = [ - { name: "Name", options: { customBodyRender: renderName } }, - "Company", - { name: "City", options: { customBodyRender: renderCities } }, - { name: "State" }, + { name: 'Name', options: { customBodyRender: renderName } }, + 'Company', + { name: 'City', options: { customBodyRender: renderCities } }, + { name: 'State' }, ]; data = [ - ["Joe James", "Test Corp", "Yonkers", "NY"], - ["John Walsh", "Test Corp", "Hartford", "CT"], - ["Bob Herm", "Test Corp", "Tampa", "FL"], - ["James Houston", "Test Corp", "Dallas", "TX"], + ['Joe James', 'Test Corp', 'Yonkers', 'NY'], + ['John Walsh', 'Test Corp', 'Hartford', 'CT'], + ['Bob Herm', 'Test Corp', 'Tampa', 'FL'], + ['James Houston', 'Test Corp', 'Dallas', 'TX'], ]; // internal table data built from source data provided displayData = JSON.stringify([ { - data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 0 }), "NY"], + data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 0 }), 'NY'], dataIndex: 0, }, { - data: ["Walsh, John", "Test Corp", renderCities("Hartford", { rowIndex: 1 }), "CT"], + data: ['Walsh, John', 'Test Corp', renderCities('Hartford', { rowIndex: 1 }), 'CT'], dataIndex: 1, }, { - data: ["Herm, Bob", "Test Corp", renderCities("Tampa", { rowIndex: 2 }), "FL"], + data: ['Herm, Bob', 'Test Corp', renderCities('Tampa', { rowIndex: 2 }), 'FL'], dataIndex: 2, }, { - data: ["Houston, James", "Test Corp", renderCities("Dallas", { rowIndex: 3 }), "TX"], + data: ['Houston, James', 'Test Corp', renderCities('Dallas', { rowIndex: 3 }), 'TX'], dataIndex: 3, }, ]); tableData = [ - { index: 0, data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 0 }), "NY"] }, - { index: 1, data: ["Walsh, John", "Test Corp", renderCities("Hartford", { rowIndex: 1 }), "CT"] }, - { index: 2, data: ["Herm, Bob", "Test Corp", renderCities("Tampa", { rowIndex: 2 }), "FL"] }, - { index: 3, data: ["Houston, James", "Test Corp", renderCities("Dallas", { rowIndex: 3 }), "TX"] }, + { index: 0, data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 0 }), 'NY'] }, + { index: 1, data: ['Walsh, John', 'Test Corp', renderCities('Hartford', { rowIndex: 1 }), 'CT'] }, + { index: 2, data: ['Herm, Bob', 'Test Corp', renderCities('Tampa', { rowIndex: 2 }), 'FL'] }, + { index: 3, data: ['Houston, James', 'Test Corp', renderCities('Dallas', { rowIndex: 3 }), 'TX'] }, ]; renderCities = renderCities; renderName = renderName; }); - it("should render a table", () => { + it('should render a table', () => { const shallowWrapper = shallow(); assert.strictEqual( shallowWrapper .dive() .dive() .name(), - "Paper", + 'Paper', ); }); - it("should correctly build internal columns data structure", () => { + it('should correctly build internal columns data structure', () => { const shallowWrapper = shallow(); const actualResult = shallowWrapper.dive().state().columns; const expectedResult = [ { - display: "true", - name: "Name", + display: 'true', + name: 'Name', sort: true, filter: true, download: true, sortDirection: null, customBodyRender: renderName, }, - { display: "true", name: "Company", sort: true, filter: true, download: true, sortDirection: null }, + { display: 'true', name: 'Company', sort: true, filter: true, download: true, sortDirection: null }, { - display: "true", - name: "City", + display: 'true', + name: 'City', sort: true, filter: true, download: true, sortDirection: null, customBodyRender: renderCities, }, - { display: "true", name: "State", sort: true, filter: true, download: true, sortDirection: null }, + { display: 'true', name: 'State', sort: true, filter: true, download: true, sortDirection: null }, ]; assert.deepEqual(actualResult, expectedResult); }); - it("should correctly build internal table data and displayData structure", () => { + it('should correctly build internal table data and displayData structure', () => { const shallowWrapper = shallow(); const state = shallowWrapper.dive().state(); //assert.deepEqual(state.data, data); assert.deepEqual(JSON.stringify(state.displayData), displayData); }); - it("should correctly re-build internal table data and displayData structure with prop change", () => { + it('should correctly re-build internal table data and displayData structure with prop change', () => { const shallowWrapper = shallow(); let state = shallowWrapper.dive().state(); @@ -117,23 +118,23 @@ describe("", function() { // now use updated props let newData = data.map(item => [...item]); - newData[0][0] = "testing"; + newData[0][0] = 'testing'; shallowWrapper.setProps({ data: newData }); shallowWrapper.update(); state = shallowWrapper.dive().state(); const expectedResult = [ - { index: 0, data: ["testing", "Test Corp", "Yonkers", "NY"] }, - { index: 1, data: ["John Walsh", "Test Corp", "Hartford", "CT"] }, - { index: 2, data: ["Bob Herm", "Test Corp", "Tampa", "FL"] }, - { index: 3, data: ["James Houston", "Test Corp", "Dallas", "TX"] }, + { index: 0, data: ['testing', 'Test Corp', 'Yonkers', 'NY'] }, + { index: 1, data: ['John Walsh', 'Test Corp', 'Hartford', 'CT'] }, + { index: 2, data: ['Bob Herm', 'Test Corp', 'Tampa', 'FL'] }, + { index: 3, data: ['James Houston', 'Test Corp', 'Dallas', 'TX'] }, ]; assert.deepEqual(state.data, expectedResult); }); - it("should not re-build internal table data and displayData structure with no prop change to data or columns", () => { - const initializeTableSpy = spy(MUIDataTable.Naked.prototype, "initializeTable"); + it('should not re-build internal table data and displayData structure with no prop change to data or columns', () => { + const initializeTableSpy = spy(MUIDataTable.Naked.prototype, 'initializeTable'); const mountWrapper = mount(shallow().get(0)); let state = mountWrapper.state(); @@ -149,7 +150,7 @@ describe("", function() { assert.deepEqual(initializeTableSpy.callCount, 1); }); - it("should correctly build internal filterList structure", () => { + it('should correctly build internal filterList structure', () => { const shallowWrapper = shallow(); const state = shallowWrapper.dive().state(); const expectedResult = [[], [], [], []]; @@ -157,20 +158,20 @@ describe("", function() { assert.deepEqual(state.filterList, expectedResult); }); - it("should correctly build internal unique column data for filterData structure", () => { + it('should correctly build internal unique column data for filterData structure', () => { const shallowWrapper = shallow(); const state = shallowWrapper.dive().state(); const expectedResult = [ - ["Herm, Bob", "Houston, James", "James, Joe", "Walsh, John"], - ["Test Corp"], - ["Dallas", "Hartford", "Tampa", "Yonkers"], - ["CT", "FL", "NY", "TX"], + ['Herm, Bob', 'Houston, James', 'James, Joe', 'Walsh, John'], + ['Test Corp'], + ['Dallas', 'Hartford', 'Tampa', 'Yonkers'], + ['CT', 'FL', 'NY', 'TX'], ]; assert.deepEqual(state.filterData, expectedResult); }); - it("should correctly build internal rowsPerPage when provided in options", () => { + it('should correctly build internal rowsPerPage when provided in options', () => { const options = { rowsPerPage: 20, textLabels, @@ -181,7 +182,7 @@ describe("", function() { assert.strictEqual(state.rowsPerPage, 20); }); - it("should correctly build internal rowsPerPageOptions when provided in options", () => { + it('should correctly build internal rowsPerPageOptions when provided in options', () => { const options = { rowsPerPageOptions: [5, 10, 15], }; @@ -191,101 +192,101 @@ describe("", function() { assert.deepEqual(state.rowsPerPageOptions, [5, 10, 15]); }); - it("should render pagination when enabled in options", () => { + it('should render pagination when enabled in options', () => { const options = { pagination: true, }; const mountWrapper = mount(); - const actualResult = mountWrapper.find(MUIDataTablePagination); + const actualResult = mountWrapper.find(TablePagination); assert.lengthOf(actualResult, 1); }); - it("should not render pagination when disabled in options", () => { + it('should not render pagination when disabled in options', () => { const options = { pagination: false, }; const mountWrapper = mount(); - const actualResult = mountWrapper.find(MUIDataTablePagination); + const actualResult = mountWrapper.find(TablePagination); assert.lengthOf(actualResult, 0); }); - it("should properly set internal filterList when calling filterUpdate method with type=checkbox", () => { + it('should properly set internal filterList when calling filterUpdate method with type=checkbox', () => { const shallowWrapper = shallow(); const table = shallowWrapper.dive(); const instance = table.instance(); - instance.filterUpdate(0, "Joe James", "checkbox"); + instance.filterUpdate(0, 'Joe James', 'checkbox'); table.update(); const state = table.state(); - assert.deepEqual(state.filterList, [["Joe James"], [], [], []]); + assert.deepEqual(state.filterList, [['Joe James'], [], [], []]); }); - it("should remove entry from filterList when calling filterUpdate method with type=checkbox and same arguments a second time", () => { + it('should remove entry from filterList when calling filterUpdate method with type=checkbox and same arguments a second time', () => { const shallowWrapper = shallow(); const table = shallowWrapper.dive(); const instance = table.instance(); - instance.filterUpdate(0, "Joe James", "checkbox"); + instance.filterUpdate(0, 'Joe James', 'checkbox'); table.update(); let state = table.state(); - assert.deepEqual(state.filterList, [["Joe James"], [], [], []]); + assert.deepEqual(state.filterList, [['Joe James'], [], [], []]); - instance.filterUpdate(0, "Joe James", "checkbox"); + instance.filterUpdate(0, 'Joe James', 'checkbox'); table.update(); state = table.state(); assert.deepEqual(state.filterList, [[], [], [], []]); }); - it("should properly set internal filterList when calling filterUpdate method with type=dropdown", () => { + it('should properly set internal filterList when calling filterUpdate method with type=dropdown', () => { const shallowWrapper = shallow(); const table = shallowWrapper.dive(); const instance = table.instance(); - instance.filterUpdate(0, "Joe James", "dropdown"); + instance.filterUpdate(0, 'Joe James', 'dropdown'); table.update(); const state = table.state(); - assert.deepEqual(state.filterList, [["Joe James"], [], [], []]); + assert.deepEqual(state.filterList, [['Joe James'], [], [], []]); }); - it("should create Chip when filterList is populated", () => { - const filterList = [["Joe James"], [], [], []]; + it('should create Chip when filterList is populated', () => { + const filterList = [['Joe James'], [], [], []]; - const mountWrapper = mount( true} />); + const mountWrapper = mount( true} />); const actualResult = mountWrapper.find(Chip); assert.strictEqual(actualResult.length, 1); }); - it("should remove entry from filterList when calling filterUpdate method with type=dropdown and same arguments a second time", () => { + it('should remove entry from filterList when calling filterUpdate method with type=dropdown and same arguments a second time', () => { const shallowWrapper = shallow(); const table = shallowWrapper.dive(); const instance = table.instance(); - instance.filterUpdate(0, "Joe James", "dropdown"); + instance.filterUpdate(0, 'Joe James', 'dropdown'); table.update(); let state = table.state(); - assert.deepEqual(state.filterList, [["Joe James"], [], [], []]); + assert.deepEqual(state.filterList, [['Joe James'], [], [], []]); - instance.filterUpdate(0, "Joe James", "dropdown"); + instance.filterUpdate(0, 'Joe James', 'dropdown'); table.update(); state = table.state(); assert.deepEqual(state.filterList, [[], [], [], []]); }); - it("should properly reset internal filterList when calling resetFilters method", () => { + it('should properly reset internal filterList when calling resetFilters method', () => { // set a filter const shallowWrapper = shallow(); const table = shallowWrapper.dive(); const instance = table.instance(); - instance.filterUpdate(0, "Joe James", "checkbox"); + instance.filterUpdate(0, 'Joe James', 'checkbox'); table.update(); // now remove it let state = table.state(); - assert.deepEqual(state.filterList, [["Joe James"], [], [], []]); + assert.deepEqual(state.filterList, [['Joe James'], [], [], []]); instance.resetFilters(); table.update(); @@ -293,23 +294,23 @@ describe("", function() { assert.deepEqual(state.filterList, [[], [], [], []]); }); - it("should properly set searchText when calling searchTextUpdate method", () => { + it('should properly set searchText when calling searchTextUpdate method', () => { const shallowWrapper = shallow(); const table = shallowWrapper.dive(); const instance = table.instance(); - instance.searchTextUpdate("Joe"); + instance.searchTextUpdate('Joe'); table.update(); const state = table.state(); const expectedResult = JSON.stringify([ - { data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 0 }), "NY"], dataIndex: 0 }, + { data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 0 }), 'NY'], dataIndex: 0 }, ]); assert.deepEqual(JSON.stringify(state.displayData), expectedResult); }); - it("should sort provided column when calling toggleSortColum method", () => { + it('should sort provided column when calling toggleSortColum method', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); @@ -318,16 +319,16 @@ describe("", function() { const state = shallowWrapper.state(); const expectedResult = JSON.stringify([ - { data: ["Herm, Bob", "Test Corp", renderCities("Tampa", { rowIndex: 0 }), "FL"], dataIndex: 2 }, - { data: ["Houston, James", "Test Corp", renderCities("Dallas", { rowIndex: 1 }), "TX"], dataIndex: 3 }, - { data: ["James, Joe", "Test Corp", renderCities("Yonkers", { rowIndex: 2 }), "NY"], dataIndex: 0 }, - { data: ["Walsh, John", "Test Corp", renderCities("Hartford", { rowIndex: 3 }), "CT"], dataIndex: 1 }, + { data: ['Herm, Bob', 'Test Corp', renderCities('Tampa', { rowIndex: 0 }), 'FL'], dataIndex: 2 }, + { data: ['Houston, James', 'Test Corp', renderCities('Dallas', { rowIndex: 1 }), 'TX'], dataIndex: 3 }, + { data: ['James, Joe', 'Test Corp', renderCities('Yonkers', { rowIndex: 2 }), 'NY'], dataIndex: 0 }, + { data: ['Walsh, John', 'Test Corp', renderCities('Hartford', { rowIndex: 3 }), 'CT'], dataIndex: 1 }, ]); assert.deepEqual(JSON.stringify(state.displayData), expectedResult); }); - it("should toggle provided column when calling toggleViewCol method", () => { + it('should toggle provided column when calling toggleViewCol method', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); @@ -337,40 +338,40 @@ describe("", function() { const expectedResult = [ { - name: "Name", - display: "false", + name: 'Name', + display: 'false', sort: true, filter: true, download: true, sortDirection: null, customBodyRender: renderName, }, - { name: "Company", display: "true", sort: true, filter: true, download: true, sortDirection: null }, + { name: 'Company', display: 'true', sort: true, filter: true, download: true, sortDirection: null }, { - name: "City", - display: "true", + name: 'City', + display: 'true', sort: true, filter: true, download: true, sortDirection: null, customBodyRender: renderCities, }, - { name: "State", display: "true", sort: true, filter: true, download: true, sortDirection: null }, + { name: 'State', display: 'true', sort: true, filter: true, download: true, sortDirection: null }, ]; assert.deepEqual(state.columns, expectedResult); }); - it("should get displayable data when calling getDisplayData method", () => { + it('should get displayable data when calling getDisplayData method', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); const state = shallowWrapper.state(); - const actualResult = instance.getDisplayData(columns, tableData, state.filterList, ""); + const actualResult = instance.getDisplayData(columns, tableData, state.filterList, ''); assert.deepEqual(JSON.stringify(actualResult), displayData); }); - it("should update rowsPerPage when calling changeRowsPerPage method", () => { + it('should update rowsPerPage when calling changeRowsPerPage method', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); @@ -381,8 +382,8 @@ describe("", function() { assert.deepEqual(state.rowsPerPage, 10); }); - it("should recalculate page when calling changeRowsPerPage method", () => { - const data = new Array(29).fill("").map(() => ["Joe James", "Test Corp", "Yonkers", "NY"]); + it('should recalculate page when calling changeRowsPerPage method', () => { + const data = new Array(29).fill('').map(() => ['Joe James', 'Test Corp', 'Yonkers', 'NY']); const mountWrapper = mount(shallow().get(0)); const instance = mountWrapper.instance(); @@ -393,7 +394,7 @@ describe("", function() { assert.equal(state.page, 1); }); - it("should update page position when calling changePage method", () => { + it('should update page position when calling changePage method', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); @@ -404,11 +405,11 @@ describe("", function() { assert.deepEqual(state.page, 2); }); - it("should update selectedRows when calling selectRowUpdate method with type=head", () => { + it('should update selectedRows when calling selectRowUpdate method with type=head', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.selectRowUpdate("head", 0); + instance.selectRowUpdate('head', 0); shallowWrapper.update(); const state = shallowWrapper.state(); @@ -421,22 +422,22 @@ describe("", function() { assert.deepEqual(state.selectedRows.data, expectedResult); }); - it("should update selectedRows when calling selectRowUpdate method with type=cell", () => { + it('should update selectedRows when calling selectRowUpdate method with type=cell', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.selectRowUpdate("cell", 0); + instance.selectRowUpdate('cell', 0); shallowWrapper.update(); const state = shallowWrapper.state(); assert.deepEqual(state.selectedRows.data, [0]); }); - it("should update selectedRows when calling selectRowUpdate method with type=custom", () => { + it('should update selectedRows when calling selectRowUpdate method with type=custom', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.selectRowUpdate("custom", [0, 3]); + instance.selectRowUpdate('custom', [0, 3]); shallowWrapper.update(); const state = shallowWrapper.state(); @@ -445,23 +446,23 @@ describe("", function() { assert.deepEqual(state.selectedRows.data, expectedResult); }); - it("should update value when calling updateValue method in customBodyRender", () => { + it('should update value when calling updateValue method in customBodyRender', () => { const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.updateDataCol(0, 2, "Las Vegas"); + instance.updateDataCol(0, 2, 'Las Vegas'); shallowWrapper.update(); const state = shallowWrapper.state(); - assert.deepEqual(state.data[0].data[2], "Las Vegas"); + assert.deepEqual(state.data[0].data[2], 'Las Vegas'); }); - it("should call onTableChange when calling selectRowUpdate method with type=head", () => { + it('should call onTableChange when calling selectRowUpdate method with type=head', () => { const options = { selectableRows: true, onTableChange: spy() }; const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.selectRowUpdate("head", 0); + instance.selectRowUpdate('head', 0); shallowWrapper.update(); const state = shallowWrapper.state(); @@ -475,13 +476,13 @@ describe("", function() { assert.strictEqual(options.onTableChange.callCount, 1); }); - it("should call onTableChange when calling selectRowUpdate method with type=cell", () => { + it('should call onTableChange when calling selectRowUpdate method with type=cell', () => { const options = { selectableRows: true, onTableChange: spy() }; const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.selectRowUpdate("cell", 0); + instance.selectRowUpdate('cell', 0); shallowWrapper.update(); const state = shallowWrapper.state(); @@ -489,12 +490,12 @@ describe("", function() { assert.strictEqual(options.onTableChange.callCount, 1); }); - it("should call onTableChange when calling selectRowUpdate method with type=custom", () => { + it('should call onTableChange when calling selectRowUpdate method with type=custom', () => { const options = { selectableRows: true, onTableChange: spy() }; const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.selectRowUpdate("custom", [0, 3]); + instance.selectRowUpdate('custom', [0, 3]); shallowWrapper.update(); const state = shallowWrapper.state(); @@ -504,36 +505,13 @@ describe("", function() { assert.strictEqual(options.onTableChange.callCount, 1); }); - describe("fallbackComparator", () => { - it("correctly compares two equal strings", () => { - expect(MUIDataTable.fallbackComparator("testString", "testString")).to.equal(0); + describe('should correctly run comparator function', () => { + it('correctly compares two equal strings', () => { + expect(getCollatorComparator()('testString', 'testString')).to.equal(0); }); - it("correctly compares two different strings", () => { - expect(MUIDataTable.fallbackComparator("testStringA", "testStringB")).to.equal(-1); - }); - }); - - describe("getCollatzComparator", () => { - describe("when Intl is available", () => { - it("returns a collator object", () => { - const comparator = MUIDataTable.getCollatzComparator(); - - expect(comparator).not.to.equal(MUIDataTable.fallbackComparator); - }); - }); - - describe("when Intl is not available", () => { - it("returns the fallback comparator", () => { - const _intl = global.Intl; - global.Intl = undefined; - - const comparator = MUIDataTable.getCollatzComparator(); - - global.Intl = _intl; - - expect(comparator).to.equal(MUIDataTable.fallbackComparator); - }); + it('correctly compares two different strings', () => { + expect(getCollatorComparator()('testStringA', 'testStringB')).to.equal(-1); }); }); }); diff --git a/test/MUIDataTableBody.test.js b/test/MUIDataTableBody.test.js index 5aa9590b1..d9d7fd20b 100644 --- a/test/MUIDataTableBody.test.js +++ b/test/MUIDataTableBody.test.js @@ -1,50 +1,51 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import textLabels from "../src/textLabels"; -import MUIDataTableBody from "../src/MUIDataTableBody"; -import MUIDataTableSelectCell from "../src/MUIDataTableSelectCell"; - -describe("", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import textLabels from '../src/textLabels'; +import TableBody from '../src/components/TableBody'; +import TableSelectCell from '../src/components/TableSelectCell'; + +describe('', function() { let data; let displayData; let columns; before(() => { - columns = [{ name: "First Name" }, { name: "Company" }, { name: "City" }, { name: "State" }]; + columns = [{ name: 'First Name' }, { name: 'Company' }, { name: 'City' }, { name: 'State' }]; data = [ - ["Joe James", "Test Corp", "Yonkers", "NY"], - ["John Walsh", "Test Corp", "Hartford", "CT"], - ["Bob Herm", "Test Corp", "Tampa", "FL"], - ["James Houston", "Test Corp", "Dallas", "TX"], + ['Joe James', 'Test Corp', 'Yonkers', 'NY'], + ['John Walsh', 'Test Corp', 'Hartford', 'CT'], + ['Bob Herm', 'Test Corp', 'Tampa', 'FL'], + ['James Houston', 'Test Corp', 'Dallas', 'TX'], ]; displayData = [ { - data: ["Joe James", "Test Corp", "Yonkers", "NY"], + data: ['Joe James', 'Test Corp', 'Yonkers', 'NY'], dataIndex: 0, }, { - data: ["John Walsh", "Test Corp", "Hartford", "CT"], + data: ['John Walsh', 'Test Corp', 'Hartford', 'CT'], dataIndex: 1, }, { - data: ["Bob Herm", "Test Corp", "Tampa", "FL"], + data: ['Bob Herm', 'Test Corp', 'Tampa', 'FL'], dataIndex: 2, }, { - data: ["James Houston", "Test Corp", "Dallas", "TX"], + data: ['James Houston', 'Test Corp', 'Dallas', 'TX'], dataIndex: 3, }, ]; }); - it("should render a table body with no selectable cells if selectableRows = false", () => { + it('should render a table body with no selectable cells if selectableRows = false', () => { const options = { selectableRows: false }; const selectRowUpdate = () => {}; + const toggleExpandRow = () => {}; const mountWrapper = mount( - ", function() { rowsPerPage={10} selectedRows={[]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ); - const actualResult = mountWrapper.find(MUIDataTableSelectCell); + const actualResult = mountWrapper.find(TableSelectCell); assert.strictEqual(actualResult.length, 0); }); - it("should render a table body with no records if no data provided", () => { + it('should render a table body with no records if no data provided', () => { const options = { selectableRows: false, textLabels }; const selectRowUpdate = () => {}; + const toggleExpandRow = () => {}; const mountWrapper = mount( - , ); const actualResult = mountWrapper.html(); - assert.include(actualResult, "Sorry, no matching records found"); + assert.include(actualResult, 'Sorry, no matching records found'); }); - it("should render a table body with selectable cells if selectableRows = true", () => { + it('should render a table body with selectable cells if selectableRows = true', () => { const options = { selectableRows: true }; const selectRowUpdate = () => {}; + const toggleExpandRow = () => {}; const mountWrapper = mount( - ", function() { rowsPerPage={10} selectedRows={[]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ); - const actualResult = mountWrapper.find(MUIDataTableSelectCell); + const actualResult = mountWrapper.find(TableSelectCell); assert.strictEqual(actualResult.length, 4); }); - it("should return the correct rowIndex when calling instance method getRowIndex", () => { + it('should return the correct rowIndex when calling instance method getRowIndex', () => { const options = { sort: true, selectableRows: true }; const selectRowUpdate = () => {}; + const toggleExpandRow = () => {}; const shallowWrapper = shallow( - ", function() { rowsPerPage={2} selectedRows={[1, 2, 3]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ).dive(); @@ -132,12 +144,13 @@ describe("", function() { assert.strictEqual(actualResult, 4); }); - it("should return correctly if row exists in selectedRows when calling instance method isRowSelected", () => { + it('should return correctly if row exists in selectedRows when calling instance method isRowSelected', () => { const options = { sort: true, selectableRows: true }; const selectRowUpdate = () => {}; + const toggleExpandRow = () => {}; const shallowWrapper = shallow( - ", function() { rowsPerPage={15} selectedRows={[1, 2, 3]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ).dive(); @@ -157,12 +172,13 @@ describe("", function() { assert.strictEqual(actualResult, false); }); - it("should trigger selectRowUpdate prop callback when calling method handleRowSelect", () => { + it('should trigger selectRowUpdate prop callback when calling method handleRowSelect', () => { const options = { sort: true, selectableRows: true }; const selectRowUpdate = spy(); + const toggleExpandRow = () => {}; const shallowWrapper = shallow( - ", function() { rowsPerPage={10} selectedRows={[]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ).dive(); @@ -183,12 +201,13 @@ describe("", function() { assert.strictEqual(selectRowUpdate.callCount, 1); }); - it("should call onRowClick when Row is clicked", () => { + it('should call onRowClick when Row is clicked', () => { const options = { selectableRows: true, onRowClick: spy() }; const selectRowUpdate = stub(); + const toggleExpandRow = () => {}; const t = mount( - ", function() { rowsPerPage={10} selectedRows={[]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ); - t.find("#MUIDataTableBodyRow-2") + t.find('#MUIDataTableBodyRow-2') .first() - .simulate("click"); + .simulate('click'); assert.strictEqual(options.onRowClick.callCount, 1); assert(options.onRowClick.calledWith(data[2], { rowIndex: 2, dataIndex: 2 })); }); it("should add custom props to rows if 'setRowProps' provided", () => { - const options = { setRowProps: stub().returns({ className: "testClass" }) }; + const options = { setRowProps: stub().returns({ className: 'testClass' }) }; const selectRowUpdate = stub(); + const toggleExpandRow = () => {}; const t = mount( - ", function() { rowsPerPage={10} selectedRows={[]} selectRowUpdate={selectRowUpdate} + expandedRows={[]} + toggleExpandRow={toggleExpandRow} options={options} - searchText={""} + searchText={''} filterList={[]} />, ); const props = t - .find("#MUIDataTableBodyRow-1") + .find('#MUIDataTableBodyRow-1') .first() .props(); - assert.strictEqual(props.className, "testClass"); + assert.strictEqual(props.className, 'testClass'); assert.isAtLeast(options.setRowProps.callCount, 1); assert(options.setRowProps.calledWith(data[1])); }); diff --git a/test/MUIDataTableFilter.test.js b/test/MUIDataTableFilter.test.js index 998481506..0e4bee29f 100644 --- a/test/MUIDataTableFilter.test.js +++ b/test/MUIDataTableFilter.test.js @@ -1,58 +1,58 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import textLabels from "../src/textLabels"; -import Select from "@material-ui/core/Select"; -import Checkbox from "@material-ui/core/Checkbox"; -import MUIDataTableFilter from "../src/MUIDataTableFilter"; - -describe("", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import textLabels from '../src/textLabels'; +import Select from '@material-ui/core/Select'; +import Checkbox from '@material-ui/core/Checkbox'; +import TableFilter from '../src/components/TableFilter'; + +describe('', function() { let data; let columns; let filterData; beforeEach(() => { columns = [ - { name: "First Name", display: true, sort: true, filter: true, sortDirection: "desc" }, - { name: "Company", display: true, sort: true, filter: true, sortDirection: "desc" }, - { name: "City", display: true, sort: true, filter: true, sortDirection: "desc" }, - { name: "State", display: true, sort: true, filter: true, sortDirection: "desc" }, + { name: 'First Name', display: true, sort: true, filter: true, sortDirection: 'desc' }, + { name: 'Company', display: true, sort: true, filter: true, sortDirection: 'desc' }, + { name: 'City', display: true, sort: true, filter: true, sortDirection: 'desc' }, + { name: 'State', display: true, sort: true, filter: true, sortDirection: 'desc' }, ]; data = [ - ["Joe James", "Test Corp", "Yonkers", "NY"], - ["John Walsh", "Test Corp", "Hartford", "CT"], - ["Bob Herm", "Test Corp", "Tampa", "FL"], - ["James Houston", "Test Corp", "Dallas", "TX"], + ['Joe James', 'Test Corp', 'Yonkers', 'NY'], + ['John Walsh', 'Test Corp', 'Hartford', 'CT'], + ['Bob Herm', 'Test Corp', 'Tampa', 'FL'], + ['James Houston', 'Test Corp', 'Dallas', 'TX'], ]; filterData = [ - ["Joe James", "John Walsh", "Bob Herm", "James Houston"], - ["Test Corp"], - ["Yonkers", "Hartford", "Tampa", "Dallas"], - ["NY", "CT", "FL", "TX"], + ['Joe James', 'John Walsh', 'Bob Herm', 'James Houston'], + ['Test Corp'], + ['Yonkers', 'Hartford', 'Tampa', 'Dallas'], + ['NY', 'CT', 'FL', 'TX'], ]; }); it("should data table filter view with checkboxes if filterType = 'checkbox'", () => { - const options = { filterType: "checkbox", textLabels }; + const options = { filterType: 'checkbox', textLabels }; const filterList = [[], [], [], []]; const shallowWrapper = mount( - , + , ); const actualResult = shallowWrapper.find(Checkbox); assert.strictEqual(actualResult.length, 13); }); - it("should data table filter view with no checkboxes if filter=false for each column", () => { - const options = { filterType: "checkbox", textLabels }; + it('should data table filter view with no checkboxes if filter=false for each column', () => { + const options = { filterType: 'checkbox', textLabels }; const filterList = [[], [], [], []]; columns = columns.map(item => (item.filter = false)); const shallowWrapper = mount( - , + , ); const actualResult = shallowWrapper.find(Checkbox); @@ -60,24 +60,24 @@ describe("", function() { }); it("should data table filter view with selects if filterType = 'select'", () => { - const options = { filterType: "select", textLabels }; - const filterList = [["Joe James"], [], [], []]; + const options = { filterType: 'select', textLabels }; + const filterList = [['Joe James'], [], [], []]; const mountWrapper = mount( - , + , ); const actualResult = mountWrapper.find(Select); assert.strictEqual(actualResult.length, 4); }); - it("should data table filter view no selects if filter=false for each column", () => { - const options = { filterType: "select", textLabels }; - const filterList = [["Joe James"], [], [], []]; + it('should data table filter view no selects if filter=false for each column', () => { + const options = { filterType: 'select', textLabels }; + const filterList = [['Joe James'], [], [], []]; columns = columns.map(item => (item.filter = false)); const mountWrapper = mount( - , + , ); const actualResult = mountWrapper.find(Select); @@ -85,24 +85,24 @@ describe("", function() { }); it("should data table filter view with checkbox selects if filterType = 'multiselect'", () => { - const options = { filterType: "multiselect", textLabels }; - const filterList = [["Joe James", "John Walsh"], [], [], []]; + const options = { filterType: 'multiselect', textLabels }; + const filterList = [['Joe James', 'John Walsh'], [], [], []]; const mountWrapper = mount( - , + , ); const actualResult = mountWrapper.find(Select); assert.strictEqual(actualResult.length, 4); }); - it("should trigger onFilterUpdate prop callback when calling method handleCheckboxChange", () => { - const options = { filterType: "checkbox", textLabels }; + it('should trigger onFilterUpdate prop callback when calling method handleCheckboxChange', () => { + const options = { filterType: 'checkbox', textLabels }; const filterList = [[], [], [], []]; const onFilterUpdate = spy(); const shallowWrapper = shallow( - ", function() { assert.strictEqual(onFilterUpdate.callCount, 1); }); - it("should trigger onFilterUpdate prop callback when calling method handleDropdownChange", () => { - const options = { filterType: "select", textLabels }; + it('should trigger onFilterUpdate prop callback when calling method handleDropdownChange', () => { + const options = { filterType: 'select', textLabels }; const filterList = [[], [], [], []]; const onFilterUpdate = spy(); const shallowWrapper = shallow( - ", function() { ).dive(); const instance = shallowWrapper.instance(); - let event = { target: { value: "All" } }; + let event = { target: { value: 'All' } }; instance.handleDropdownChange(event, 0); assert.strictEqual(onFilterUpdate.callCount, 1); - event = { target: { value: "test" } }; + event = { target: { value: 'test' } }; instance.handleDropdownChange(event, 0); assert.strictEqual(onFilterUpdate.callCount, 2); }); diff --git a/test/MUIDataTableHead.test.js b/test/MUIDataTableHead.test.js index 7c23db8e4..8b227c72f 100644 --- a/test/MUIDataTableHead.test.js +++ b/test/MUIDataTableHead.test.js @@ -1,32 +1,32 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import MUIDataTableHead from "../src/MUIDataTableHead"; -import MUIDataTableHeadCell from "../src/MUIDataTableHeadCell"; -import Tooltip from "@material-ui/core/Tooltip"; +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import TableHead from '../src/components/TableHead'; +import TableHeadCell from '../src/components/TableHeadCell'; +import Tooltip from '@material-ui/core/Tooltip'; -describe("", function() { +describe('', function() { let columns; let handleHeadUpdateRef; before(() => { columns = [ - { name: "First Name", display: "true", sort: null }, - { name: "Company", display: "true", sort: null }, - { name: "City", display: "true", sort: null }, - { name: "State", display: "true", sort: null }, + { name: 'First Name', display: 'true', sort: null }, + { name: 'Company', display: 'true', sort: null }, + { name: 'City', display: 'true', sort: null }, + { name: 'State', display: 'true', sort: null }, ]; handleHeadUpdateRef = () => {}; }); - it("should render a table head", () => { + it('should render a table head', () => { const options = {}; const toggleSort = () => {}; const mountWrapper = mount( - {}} @@ -34,17 +34,17 @@ describe("", function() { toggleSort={toggleSort} />, ); - const actualResult = mountWrapper.find(MUIDataTableHeadCell); + const actualResult = mountWrapper.find(TableHeadCell); assert.strictEqual(actualResult.length, 4); }); - it("should render a table head with no cells", () => { + it('should render a table head with no cells', () => { const options = {}; const toggleSort = () => {}; const newColumns = columns.map(column => ({ ...column, display: false })); const mountWrapper = mount( - {}} @@ -52,16 +52,16 @@ describe("", function() { toggleSort={toggleSort} />, ); - const actualResult = mountWrapper.find(MUIDataTableHeadCell); + const actualResult = mountWrapper.find(TableHeadCell); assert.strictEqual(actualResult.length, 0); }); - it("should trigger toggleSort prop callback when calling method handleToggleColumn", () => { + it('should trigger toggleSort prop callback when calling method handleToggleColumn', () => { const options = { sort: true }; const toggleSort = spy(); const shallowWrapper = shallow( - {}} @@ -77,12 +77,12 @@ describe("", function() { assert.strictEqual(toggleSort.callCount, 1); }); - it("should trigger selectRowUpdate prop callback and selectChecked state update when calling method handleRowSelect", () => { + it('should trigger selectRowUpdate prop callback and selectChecked state update when calling method handleRowSelect', () => { const options = { sort: true, selectableRows: true }; const rowSelectUpdate = spy(); const shallowWrapper = shallow( - {}} diff --git a/test/MUIDataTableHeadCell.test.js b/test/MUIDataTableHeadCell.test.js index 019091a24..d7e4c3b27 100644 --- a/test/MUIDataTableHeadCell.test.js +++ b/test/MUIDataTableHeadCell.test.js @@ -1,13 +1,13 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import textLabels from "../src/textLabels"; -import MUIDataTableHeadCell from "../src/MUIDataTableHeadCell"; -import TableSortLabel from "@material-ui/core/TableSortLabel"; -import HelpIcon from "@material-ui/icons/Help"; - -describe("", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import textLabels from '../src/textLabels'; +import TableHeadCell from '../src/components/TableHeadCell'; +import TableSortLabel from '@material-ui/core/TableSortLabel'; +import HelpIcon from '@material-ui/icons/Help'; + +describe('', function() { let classes; before(() => { @@ -16,83 +16,73 @@ describe("", function() { }; }); - it("should render a table head cell with sort label when options.sort = true provided", () => { + it('should render a table head cell with sort label when options.sort = true provided', () => { const options = { sort: true, textLabels }; const toggleSort = () => {}; const shallowWrapper = shallow( - + some content - , + , ).dive(); const actualResult = shallowWrapper.find(TableSortLabel); assert.strictEqual(actualResult.length, 1); }); - it("should render a table head cell without sort label when options.sort = false provided", () => { + it('should render a table head cell without sort label when options.sort = false provided', () => { const options = { sort: false, textLabels }; const toggleSort = () => {}; const shallowWrapper = shallow( - + some content - , + , ); const actualResult = shallowWrapper.find(TableSortLabel); assert.strictEqual(actualResult.length, 0); }); - it("should render a table help icon when hint provided", () => { + it('should render a table help icon when hint provided', () => { const options = { sort: true, textLabels }; const shallowWrapper = shallow( - + some content - , + , ).dive(); const actualResult = shallowWrapper.find(HelpIcon); assert.strictEqual(actualResult.length, 1); }); - it("should render a table head cell without custom tooltip when hint provided", () => { + it('should render a table head cell without custom tooltip when hint provided', () => { const options = { sort: true, textLabels }; const shallowWrapper = shallow( - + some content - , + , ).dive(); const actualResult = shallowWrapper.find(HelpIcon); assert.strictEqual(actualResult.length, 0); }); - it("should trigger toggleSort prop callback when calling method handleSortClick", () => { + it('should trigger toggleSort prop callback when calling method handleSortClick', () => { const options = { sort: true, textLabels }; const toggleSort = spy(); const shallowWrapper = shallow( - + some content - , + , ).dive(); const instance = shallowWrapper.instance(); - const event = { target: { value: "All" } }; + const event = { target: { value: 'All' } }; instance.handleSortClick(); assert.strictEqual(toggleSort.callCount, 1); }); diff --git a/test/MUIDataTablePagination.test.js b/test/MUIDataTablePagination.test.js index d2b584b5c..a5a6e9bfd 100644 --- a/test/MUIDataTablePagination.test.js +++ b/test/MUIDataTablePagination.test.js @@ -1,14 +1,14 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import TableRow from "@material-ui/core/TableRow"; -import TableFooter from "@material-ui/core/TableFooter"; -import TablePagination from "@material-ui/core/TablePagination"; -import textLabels from "../src/textLabels"; -import MUIDataTablePagination from "../src/MUIDataTablePagination"; - -describe("", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import TableRow from '@material-ui/core/TableRow'; +import TableFooter from '@material-ui/core/TableFooter'; +import MuiTablePagination from '@material-ui/core/TablePagination'; +import textLabels from '../src/textLabels'; +import TablePagination from '../src/components/TablePagination'; + +describe('', function() { let options; before(() => { @@ -18,34 +18,28 @@ describe("", function() { }; }); - it("should render a table footer with pagination", () => { - const mountWrapper = mount(); + it('should render a table footer with pagination', () => { + const mountWrapper = mount(); - const actualResult = mountWrapper.find(TablePagination); + const actualResult = mountWrapper.find(MuiTablePagination); assert.strictEqual(actualResult.length, 1); }); - it("should trigger changeRowsPerPage prop callback when calling method handleRowChange", () => { + it('should trigger changeRowsPerPage prop callback when calling method handleRowChange', () => { const changeRowsPerPage = spy(); const shallowWrapper = shallow( - , + , ).dive(); const instance = shallowWrapper.instance(); - instance.handleRowChange({ target: { value: "" } }); + instance.handleRowChange({ target: { value: '' } }); assert.strictEqual(changeRowsPerPage.callCount, 1); }); - it("should trigger changePage prop callback when calling method handlePageChange", () => { + it('should trigger changePage prop callback when calling method handlePageChange', () => { const changePage = spy(); const shallowWrapper = shallow( - , + , ).dive(); const instance = shallowWrapper.instance(); diff --git a/test/MUIDataTableSearch.test.js b/test/MUIDataTableSearch.test.js index 8aeb208e0..d75245e0c 100644 --- a/test/MUIDataTableSearch.test.js +++ b/test/MUIDataTableSearch.test.js @@ -1,54 +1,54 @@ -import React from "react"; -import simulant from "simulant"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import TextField from "@material-ui/core/TextField"; -import MUIDataTableSearch from "../src/MUIDataTableSearch"; -import textLabels from "../src/textLabels"; - -describe("", function() { - it("should render a search bar", () => { +import React from 'react'; +import simulant from 'simulant'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import TextField from '@material-ui/core/TextField'; +import TableSearch from '../src/components/TableSearch'; +import textLabels from '../src/textLabels'; + +describe('', function() { + it('should render a search bar', () => { const options = { textLabels }; const onSearch = () => {}; const onHide = () => {}; - const mountWrapper = mount(); + const mountWrapper = mount(); const actualResult = mountWrapper.find(TextField); assert.strictEqual(actualResult.length, 1); }); - it("should trigger handleTextChange prop callback when calling method handleTextChange", () => { + it('should trigger handleTextChange prop callback when calling method handleTextChange', () => { const options = { onSearchChange: () => true, textLabels }; const onSearch = spy(); const onHide = () => {}; - const shallowWrapper = shallow().dive(); + const shallowWrapper = shallow().dive(); const instance = shallowWrapper.instance(); - instance.handleTextChange({ target: { value: "" } }); + instance.handleTextChange({ target: { value: '' } }); assert.strictEqual(onSearch.callCount, 1); }); - it("should hide the search bar when hitting the ESCAPE key", () => { + it('should hide the search bar when hitting the ESCAPE key', () => { const options = { textLabels }; const onHide = spy(); - const mountWrapper = mount(, { attachTo: document.body }); + const mountWrapper = mount(, { attachTo: document.body }); - simulant.fire(document.body.querySelector("input"), "keydown", { keyCode: 27 }); + simulant.fire(document.body.querySelector('input'), 'keydown', { keyCode: 27 }); assert.strictEqual(onHide.callCount, 1); }); - it("should hide not hide search bar when entering anything but the ESCAPE key", () => { + it('should hide not hide search bar when entering anything but the ESCAPE key', () => { const options = { textLabels }; const onHide = spy(); - const mountWrapper = mount(, { attachTo: document.body }); + const mountWrapper = mount(, { attachTo: document.body }); - simulant.fire(document.body.querySelector("input"), "keydown", { keyCode: 25 }); + simulant.fire(document.body.querySelector('input'), 'keydown', { keyCode: 25 }); assert.strictEqual(onHide.callCount, 0); }); }); diff --git a/test/MUIDataTableSelectCell.test.js b/test/MUIDataTableSelectCell.test.js index 4451128b3..54b3ce70a 100644 --- a/test/MUIDataTableSelectCell.test.js +++ b/test/MUIDataTableSelectCell.test.js @@ -1,29 +1,29 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import Checkbox from "@material-ui/core/Checkbox"; -import MUIDataTableSelectCell from "../src/MUIDataTableSelectCell"; - -describe("", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import Checkbox from '@material-ui/core/Checkbox'; +import TableSelectCell from '../src/components/TableSelectCell'; + +describe('', function() { before(() => {}); - it("should render table select cell", () => { - const mountWrapper = mount(); + it('should render table select cell', () => { + const mountWrapper = mount(); const actualResult = mountWrapper.find(Checkbox); assert.strictEqual(actualResult.length, 1); }); - it("should render table select cell checked", () => { - const mountWrapper = mount(); + it('should render table select cell checked', () => { + const mountWrapper = mount(); const actualResult = mountWrapper.find(Checkbox); assert.strictEqual(actualResult.props().checked, true); }); - it("should render table select cell unchecked", () => { - const mountWrapper = mount(); + it('should render table select cell unchecked', () => { + const mountWrapper = mount(); const actualResult = mountWrapper.find(Checkbox); assert.strictEqual(actualResult.props().checked, false); diff --git a/test/MUIDataTableToolbar.test.js b/test/MUIDataTableToolbar.test.js index 35b80b53f..1951e2dd4 100644 --- a/test/MUIDataTableToolbar.test.js +++ b/test/MUIDataTableToolbar.test.js @@ -1,22 +1,23 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import IconButton from "@material-ui/core/IconButton"; -import SearchIcon from "@material-ui/icons/Search"; -import DownloadIcon from "@material-ui/icons/CloudDownload"; -import PrintIcon from "@material-ui/icons/Print"; -import ViewColumnIcon from "@material-ui/icons/ViewColumn"; -import ClearIcon from "@material-ui/icons/Clear"; -import FilterIcon from "@material-ui/icons/FilterList"; -import MUIDataTableToolbar from "../src/MUIDataTableToolbar"; -import MUIDataTableSearch from "../src/MUIDataTableSearch"; -import textLabels from "../src/textLabels"; - -describe("", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import IconButton from '@material-ui/core/IconButton'; +import SearchIcon from '@material-ui/icons/Search'; +import DownloadIcon from '@material-ui/icons/CloudDownload'; +import PrintIcon from '@material-ui/icons/Print'; +import ViewColumnIcon from '@material-ui/icons/ViewColumn'; +import ClearIcon from '@material-ui/icons/Clear'; +import FilterIcon from '@material-ui/icons/FilterList'; +import TableToolbar from '../src/components/TableToolbar'; +import TableSearch from '../src/components/TableSearch'; +import textLabels from '../src/textLabels'; + +describe('', function() { let data; let columns; let options; + let setTableAction = () => {}; before(() => { options = { @@ -27,99 +28,109 @@ describe("", function() { viewColumns: true, textLabels, downloadOptions: { - separator: ",", - filename: "tableDownload.csv", + separator: ',', + filename: 'tableDownload.csv', }, }; - columns = ["First Name", "Company", "City", "State"]; + columns = ['First Name', 'Company', 'City', 'State']; data = [ { - data: ["Joe James", "Test Corp", "Yonkers", "NY"], + data: ['Joe James', 'Test Corp', 'Yonkers', 'NY'], dataIndex: 0, }, { - data: ["John Walsh", "Test Corp", "Hartford", "CT"], + data: ['John Walsh', 'Test Corp', 'Hartford', 'CT'], dataIndex: 1, }, { - data: ["Bob Herm", "Test Corp", "Tampa", "FL"], + data: ['Bob Herm', 'Test Corp', 'Tampa', 'FL'], dataIndex: 2, }, { - data: ["James Houston", "Test Corp", "Dallas", "TX"], + data: ['James Houston', 'Test Corp', 'Dallas', 'TX'], dataIndex: 3, }, ]; }); - it("should render a toolbar", () => { + it('should render a toolbar', () => { const mountWrapper = mount( - {}} columns={columns} data={data} options={options} />, + , ); const actualResult = mountWrapper.find(IconButton); assert.strictEqual(actualResult.length, 5); }); - it("should render a toolbar with no search icon if option.search = false", () => { + it('should render a toolbar with no search icon if option.search = false', () => { const newOptions = { ...options, search: false }; - const mountWrapper = mount(); + const mountWrapper = mount( + , + ); const actualResult = mountWrapper.find(SearchIcon); assert.strictEqual(actualResult.length, 0); }); - it("should render a toolbar with no download icon if option.download = false", () => { + it('should render a toolbar with no download icon if option.download = false', () => { const newOptions = { ...options, download: false }; - const mountWrapper = mount(); + const mountWrapper = mount( + , + ); const actualResult = mountWrapper.find(DownloadIcon); assert.strictEqual(actualResult.length, 0); }); - it("should render a toolbar with no print icon if option.print = false", () => { + it('should render a toolbar with no print icon if option.print = false', () => { const newOptions = { ...options, print: false }; - const mountWrapper = mount(); + const mountWrapper = mount( + , + ); const actualResult = mountWrapper.find(PrintIcon); assert.strictEqual(actualResult.length, 0); }); - it("should render a toolbar with no view columns icon if option.viewColumns = false", () => { + it('should render a toolbar with no view columns icon if option.viewColumns = false', () => { const newOptions = { ...options, viewColumns: false }; - const mountWrapper = mount(); + const mountWrapper = mount( + , + ); const actualResult = mountWrapper.find(ViewColumnIcon); assert.strictEqual(actualResult.length, 0); }); - it("should render a toolbar with no filter icon if option.filter = false", () => { + it('should render a toolbar with no filter icon if option.filter = false', () => { const newOptions = { ...options, filter: false }; - const mountWrapper = mount(); + const mountWrapper = mount( + , + ); const actualResult = mountWrapper.find(FilterIcon); assert.strictEqual(actualResult.length, 0); }); - it("should render a toolbar with a search clicking search icon", () => { + it('should render a toolbar with a search clicking search icon', () => { const shallowWrapper = shallow( - {}} data={data} options={options} />, + , ) .dive() .dive() .dive(); const instance = shallowWrapper.instance(); - instance.setActiveIcon("search"); + instance.setActiveIcon('search'); shallowWrapper.update(); - const actualResult = shallowWrapper.find(MUIDataTableSearch); + const actualResult = shallowWrapper.find(TableSearch); assert.strictEqual(actualResult.length, 1); }); - it("should hide search after clicking cancel icon", () => { + it('should hide search after clicking cancel icon', () => { const searchTextUpdate = () => {}; const shallowWrapper = shallow( - {}} columns={columns} data={data} options={options} + setTableAction={setTableAction} />, ) .dive() @@ -132,37 +143,45 @@ describe("", function() { }; // display search - instance.setActiveIcon("search"); + instance.setActiveIcon('search'); shallowWrapper.update(); - let actualResult = shallowWrapper.find(MUIDataTableSearch); + let actualResult = shallowWrapper.find(TableSearch); assert.strictEqual(actualResult.length, 1); // now hide it and test instance.hideSearch(); shallowWrapper.update(); - actualResult = shallowWrapper.find(MUIDataTableSearch); + actualResult = shallowWrapper.find(TableSearch); assert.strictEqual(actualResult.length, 0); }); - it("should set icon when calling method setActiveIcon", () => { - const shallowWrapper = shallow() + it('should set icon when calling method setActiveIcon', () => { + const shallowWrapper = shallow( + , + ) .dive() .dive() .dive(); const instance = shallowWrapper.instance(); - instance.setActiveIcon("filter"); + instance.setActiveIcon('filter'); shallowWrapper.update(); const state = shallowWrapper.state(); - assert.strictEqual(state.iconActive, "filter"); + assert.strictEqual(state.iconActive, 'filter'); }); - it("should download CSV when calling method handleCSVDownload", () => { + it('should download CSV when calling method handleCSVDownload', () => { const shallowWrapper = shallow( - , + , ); const instance = shallowWrapper .dive() @@ -170,8 +189,8 @@ describe("", function() { .dive() .instance(); - const appendSpy = spy(document.body, "appendChild"); - const removeSpy = spy(document.body, "removeChild"); + const appendSpy = spy(document.body, 'appendChild'); + const removeSpy = spy(document.body, 'removeChild'); instance.handleCSVDownload(); assert.strictEqual(appendSpy.callCount, 1); diff --git a/test/MUIDataTableToolbarSelect.test.js b/test/MUIDataTableToolbarSelect.test.js index 4e8c3a71c..cafccb472 100644 --- a/test/MUIDataTableToolbarSelect.test.js +++ b/test/MUIDataTableToolbarSelect.test.js @@ -1,32 +1,32 @@ -import React from "react"; -import { match, spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import DeleteIcon from "@material-ui/icons/Delete"; -import MUIDataTableToolbarSelect from "../src/MUIDataTableToolbarSelect"; -import textLabels from "../src/textLabels"; +import React from 'react'; +import { match, spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import DeleteIcon from '@material-ui/icons/Delete'; +import TableToolbarSelect from '../src/components/TableToolbarSelect'; +import textLabels from '../src/textLabels'; -describe("", function() { +describe('', function() { before(() => {}); - it("should render table toolbar select", () => { + it('should render table toolbar select', () => { const onRowsDelete = () => {}; const mountWrapper = mount( - , + , ); const actualResult = mountWrapper.find(DeleteIcon); assert.strictEqual(actualResult.length, 1); }); - it("should call customToolbarSelect with 3 arguments", () => { + it('should call customToolbarSelect with 3 arguments', () => { const onRowsDelete = () => {}; const customToolbarSelect = spy(); const selectedRows = { data: [1] }; const displayData = [1]; const mountWrapper = mount( - ", function() { />, ); - assert.strictEqual(customToolbarSelect.calledWith(selectedRows, displayData, match.typeOf("function")), true); + assert.strictEqual(customToolbarSelect.calledWith(selectedRows, displayData, match.typeOf('function')), true); }); - it("should throw TypeError if selectedRows is not an array of numbers", done => { + it('should throw TypeError if selectedRows is not an array of numbers', done => { const onRowsDelete = () => {}; const selectRowUpdate = () => {}; const customToolbarSelect = (_, __, setSelectedRows) => { const spySetSelectedRows = spy(setSelectedRows); try { - spySetSelectedRows(""); + spySetSelectedRows(''); } catch (error) { //do nothing } try { - spySetSelectedRows(["1"]); + spySetSelectedRows(['1']); } catch (error) { //do nothing } @@ -61,7 +61,7 @@ describe("", function() { const displayData = [1]; const mountWrapper = mount( - ", function() { ); }); - it("should call selectRowUpdate when customToolbarSelect passed and setSelectedRows was called", () => { + it('should call selectRowUpdate when customToolbarSelect passed and setSelectedRows was called', () => { const onRowsDelete = () => {}; const selectRowUpdate = spy(); const customToolbarSelect = (_, __, setSelectedRows) => { @@ -81,7 +81,7 @@ describe("", function() { const displayData = [1]; const mountWrapper = mount( - ", function() { +import React from 'react'; +import { spy, stub } from 'sinon'; +import { mount, shallow } from 'enzyme'; +import { assert, expect, should } from 'chai'; +import Checkbox from '@material-ui/core/Checkbox'; +import TableViewCol from '../src/components/TableViewCol'; +import textLabels from '../src/textLabels'; + +describe('', function() { let columns; let options; before(() => { - columns = ["a", "b", "c", "d"]; + columns = ['a', 'b', 'c', 'd']; options = { textLabels, }; }); - it("should render view columns", () => { - const mountWrapper = mount(); + it('should render view columns', () => { + const mountWrapper = mount(); const actualResult = mountWrapper.find(Checkbox); assert.strictEqual(actualResult.length, 4); }); - it("should trigger onColumnUpdate prop callback when calling method handleColChange", () => { + it('should trigger onColumnUpdate prop callback when calling method handleColChange', () => { const onColumnUpdate = spy(); const shallowWrapper = shallow( - , + , ).dive(); const instance = shallowWrapper.instance(); diff --git a/test/MUIPopover.test.js b/test/MUIPopover.test.js deleted file mode 100644 index 09e8965e2..000000000 --- a/test/MUIPopover.test.js +++ /dev/null @@ -1,107 +0,0 @@ -import React from "react"; -import { spy, stub } from "sinon"; -import { mount, shallow } from "enzyme"; -import { assert, expect, should } from "chai"; -import { MUIPopover, MUIPopoverTarget, MUIPopoverContent } from "../src/MUIPopover"; -import Popover from "@material-ui/core/Popover"; - -describe("", function() { - it("should render a popover", () => { - const mountWrapper = mount( - - - Simple Link! - - Some content - , - ); - - const actualResult = mountWrapper.find(Popover); - assert.strictEqual(actualResult.length, 1); - }); - - it("should not render a popover if children are not MUIPopoverContent or MUIPopoverTarget", () => { - stub(console, "error"); - const mountWrapper = mount( - -
testing
-
, - ); - - assert(console.error.called); - console.error.restore(); - }); - - it("should return children when calling MUIPopoverContent", () => { - const shallowWrapper = shallow(Some content); - - assert.strictEqual(shallowWrapper.text(), "Some content"); - }); - - it("should call handleOnExit when unmounting MUIPopover", () => { - const exitFunc = spy(); - const shallowWrapper = shallow( - - - Simple Link! - - Some content - , - ); - - const instance = shallowWrapper.instance(); - instance.handleOnExit(); - assert.strictEqual(exitFunc.callCount, 1); - }); - - it("should close popover when calling method handleRequestClose", () => { - const refClose = spy(); - const mountWrapper = mount( - - - Simple Link! - - Some content - , - ); - - // open popover - mountWrapper.setState({ open: true }); - mountWrapper.update(); - let state = mountWrapper.state(); - - assert.strictEqual(state.open, true); - assert.strictEqual(mountWrapper.find(Popover).length, 1); - - // hide popover - const instance = mountWrapper.instance(); - instance.handleRequestClose(); - mountWrapper.update(); - state = mountWrapper.state(); - - assert.strictEqual(state.open, false); - assert.strictEqual(refClose.callCount, 1); - }); - - it("should open popover when calling method handleClick", () => { - const mountWrapper = mount( - - - Simple Link! - - Some content - , - ); - - let state = mountWrapper.state(); - const instance = mountWrapper.instance(); - assert.strictEqual(state.open, false); - - instance.handleClick(); - mountWrapper.update(); - - state = mountWrapper.state(); - assert.strictEqual(state.open, true); - assert.strictEqual(mountWrapper.find(Popover).length, 1); - }); -}); diff --git a/test/setup-mocha-env.js b/test/setup-mocha-env.js index 8482b14f6..c56683a5e 100644 --- a/test/setup-mocha-env.js +++ b/test/setup-mocha-env.js @@ -1,35 +1,35 @@ -import Enzyme from "enzyme"; -import React from "react"; -import Adapter from "enzyme-adapter-react-16"; +import Enzyme from 'enzyme'; +import React from 'react'; +import Adapter from 'enzyme-adapter-react-16'; /* required when running >= 16.0 */ Enzyme.configure({ adapter: new Adapter() }); function setupDom() { - const { JSDOM } = require("jsdom"); - const Node = require("jsdom/lib/jsdom/living/node-document-position"); + const { JSDOM } = require('jsdom'); + const Node = require('jsdom/lib/jsdom/living/node-document-position'); - const dom = new JSDOM(""); + const dom = new JSDOM(''); global.window = dom.window; global.document = window.document; global.Node = Node; global.navigator = { - userAgent: "node.js", - appVersion: "", + userAgent: 'node.js', + appVersion: '', }; function copyProps(src, target) { const props = Object.getOwnPropertyNames(src) - .filter(prop => typeof target[prop] === "undefined") + .filter(prop => typeof target[prop] === 'undefined') .map(prop => Object.getOwnPropertyDescriptor(src, prop)); Object.defineProperties(target, props); } copyProps(dom.window, global); - const KEYS = ["HTMLElement"]; + const KEYS = ['HTMLElement']; KEYS.forEach(key => { global[key] = window[key]; }); @@ -38,11 +38,11 @@ function setupDom() { setStart: () => {}, setEnd: () => {}, commonAncestorContainer: { - nodeName: "BODY", + nodeName: 'BODY', ownerDocument: { documentElement: window.document.body, parent: { - nodeName: "BODY", + nodeName: 'BODY', }, }, }, @@ -55,8 +55,8 @@ function setupDom() { global.window.cancelAnimationFrame = () => {}; global.getComputedStyle = global.window.getComputedStyle; - Object.defineProperty(global.window.URL, "createObjectURL", { value: () => {} }); - global.Blob = () => ""; + Object.defineProperty(global.window.URL, 'createObjectURL', { value: () => {} }); + global.Blob = () => ''; } setupDom(); diff --git a/webpack.config.js b/webpack.config.js index ad79743b2..001e1b037 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,7 +3,7 @@ const webpack = require('webpack'); module.exports = { entry: { - app: "./examples/fixed-header/index.js" + app: "./examples/expandable-rows/index.js" }, stats: "verbose", context: __dirname,