Reputation: 7575
I have a Material-UI’s <Table>
, and in each <TableRow>
(which is dynamically rendered) for the <TableBody>
, I would like to have a button (<FlatButton>
) for one of the columns. And once the button is clicked on, it will open up a <Dialog>
and inside it would like to have a working <Tabs>
.
So how can I display a <FlatButton>
for each row for a particular column, and when the button is clicked on, display the <Dialog>
along with a working <Tabs>
on the inside as the content? And have the <Dialog>
close when clicked on outside?
So far I have the following, but came across the following issues: the opens up but it is slow and clicking outside the <Dialog>
is not closing it, the <Tabs>
is visible but it is not working:
Main Table:
import React, { Component } from 'react'
import {
Subheader,
Table,
TableBody,
TableHeader,
TableHeaderColumn,
TableRow,
} from 'material-ui'
import RenderedTableRow from ‘./RenderedTableRow'
export default class MainTable extends Component {
constructor() {
super()
}
render() {
return (
<div>
<div>
<Subheader>Table</Subheader>
<Table
multiSelectable={true}
>
<TableHeader
displaySelectAll={true}
enableSelectAll={true}
>
<TableRow>
<TableHeaderColumn>
Col 1
</TableHeaderColumn>
<TableHeaderColumn>
Col 2
</TableHeaderColumn>
<TableHeaderColumn>
Col 3
</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody
deselectOnClickaway={false}
stripedRows
>
<RenderedTableRow {...this.props}/>
</TableBody>
</Table>
</div>
</div>
)
}
}
Rendered Table Row:
import React, { Component } from 'react'
import { Dialog, FlatButton, Tabs, Tab, TableRow, TableRowColumn } from 'material-ui'
import ContentAdd from 'material-ui/svg-icons/content/add';
export default class RenderedTableRow extends Component {
constructor(props) {
super(props)
this.state = {
open: false,
}
this._handleOpen = this._handleOpen.bind(this)
this._handleClose = this._handleClose.bind(this)
}
_handleOpen() {
this.setState({
open: true
})
}
_handleClose() {
this.setState({
open: false
})
}
render() {
const {
children,
...rest
} = this.props
const actions = [
<FlatButton
label="Cancel"
primary={true}
onClick={this._handleClose}
/>,
]
return (
<TableRow {...rest}>
{children[0]}
<TableRowColumn>Red</TableRowColumn>
<TableRowColumn>John, Joshua</TableRowColumn>
<TableRowColumn>
<FlatButton
icon={<ContentAdd/>}
onClick={this._handleOpen}
/>
</TableRowColumn>
<Dialog
actions={actions}
autoScrollBodyContent={true}
open={this.state.open}
onRequestClose={this._handleClose}
modal={false}
title='Test'
>
<Tabs>
<Tab label="Item One" >
<div>
<h2 >Tab One</h2>
<p>
This is an example tab.
</p>
</div>
</Tab>
<Tab label="Item Two" >
<div>
<h2>Tab Two</h2>
<p>
This is another example tab.
</p>
</div>
</Tab>
</Tabs>
</Dialog>
</TableRow>
)
}
}
Thank you in advance and will accept/upvote answer.
Upvotes: 8
Views: 4939
Reputation: 1051
As I mentioned earlier in the comments, you should have only one Dialog
element along with the table component. Embedding Dialog
in each row will impact the performance and is general a bad practice. Here is the solution for mocked tabs:
import React, { Component } from 'react';
import { find } from 'lodash';
import { Dialog, FlatButton, Tabs, Tab, TableRow, TableRowColumn } from 'material-ui';
import ContentAdd from 'material-ui/svg-icons/content/add';
class MainTable extends Component {
// mocked data to show you the example:
static fields = [{
id: 1,
name: 'John',
tabs: [{
header: 'Tab 1 John',
content: 'Content of tab 1 for John'
}, {
header: 'Tab 2 John',
content: 'Content of tab 2 for John'
}]
}, {
id: 2,
name: 'George',
tabs: [{
header: 'Tab 1 George',
content: 'Content of tab 1 for George'
}, {
header: 'Tab 2 George',
content: 'Content of tab 2 for George'
}]
}];
state = {
activeRowId: null // we will store the `id` of the active row (opened dialog)
};
handleOpen = (rowId) => () => {
this.setState({
activeRowId: rowId // set `id` taken from the row
});
};
handleClose = () => {
this.setState({
activeRowId: null // reset active `id`
});
};
renderRows = (field) => (
<TableRow key={`row-${field.id}`}>
<TableRowColumn>{field.name}</TableRowColumn>
<TableRowColumn>
<FlatButton
icon={<ContentAdd />}
onClick={this.handleOpen(field.id)}
/>
</TableRowColumn>
</TableRow>
);
render() {
const rows = MainTable.fields.map(this.renderRows);
const { activeRowId } = this.state;
const actions = [
<FlatButton
label="Cancel"
primary
onTouchTap={this.handleClose}
/>,
<FlatButton
label="Submit"
primary
keyboardFocused
onTouchTap={this.handleClose}
/>,
];
const activeRow = find(MainTable.fields, { id: activeRowId }); // find the data for this active row `id`
return (
<div>
{rows}
{activeRow ? (
<Dialog
title="Dialog title"
actions={actions}
modal={false}
open
onRequestClose={this.handleClose}
>
<Tabs>
{activeRow.tabs.map((tab, index) => (
<Tab label={tab.header} key={`tab-${index}`}>
<div>
<h2>{tab.header}</h2>
<p>{tab.content}</p>
</div>
</Tab>
))}
</Tabs>
</Dialog>
) : null}
</div>
);
}
}
export default MainTable;
This is how it works right now:
Few remarks:
key
prop in all the elements of the list (where you iterate over a list) - both rows and tabs (key
is missing in your question).Upvotes: 1
Reputation: 21
Here is a working example below, it should work straight via copy pasta.
The answer to your question is that you need to be able to differentiate between the different rows
, setting it to true
will display all dialogs, or possibly just the last. Once you differentiate it, displaying the dialog
you want shouldn't be a problem. There are ways to just have one dialog
and still have this work, but I'll let you figure it out.
Somethings to note, is that you can definitely clean this code up. Create separate files for creating TableRows
TableColumns
etc.
I left it at two columns for now, however you should be able to understand the code. Feel free to ask any additional questions.
import React, { Component } from 'react'
import { Dialog, FlatButton, Tabs, Tab, TableRow, TableRowColumn } from 'material-ui'
import ContentAdd from 'material-ui/svg-icons/content/add';
class MainTable extends Component {
static fields = [{tab1:"a", tab2:"b"}, {tab1:"c", tab2:"d"}];
state = {
open: false,
}
handleOpen = (field) => () => {
this.setState({
open: field
})
}
handleClose = () => {
this.setState({
open: false
})
}
renderRows = (field) => {
const { open } = this.state;
const actions = [
<FlatButton
label="Cancel"
primary={true}
onTouchTap={this.handleClose}
/>,
<FlatButton
label="Submit"
primary={true}
keyboardFocused={true}
onTouchTap={this.handleClose}
/>,
];
return (<TableRow key={field.tab1}>
<TableRowColumn>{field.tab1}</TableRowColumn>
<TableRowColumn>
<FlatButton
icon={<ContentAdd/>}
onClick={this.handleOpen(field.tab1)}
/>
</TableRowColumn>
<Dialog
title="Dialog With Actions"
actions={actions}
modal={false}
open={open === field.tab1}
onRequestClose={this.handleClose}
>
<Tabs>
<Tab label={field.tab1} >
<div>
<h2>{field.tab1}</h2>
<p>
This is one tab.
</p>
</div>
</Tab>
<Tab label={field.tab2}>
<div>
<h2>{field.tab2}</h2>
<p>
This is another example tab.
</p>
</div>
</Tab>
</Tabs>
</Dialog>
</TableRow>);
}
render() {
const rows = MainTable.fields.map(this.renderRows);
return (
<div>
{rows}
</div>
)
}
}
export default MainTable;
Upvotes: 2
Reputation: 789
You should probably only have one dialog for the whole table that lives in your MainTable
component. This is more efficient because you don't need a dialog per row but only one dialog.
In order for the button in the RenderedTableRow
to open the modal and tell it which row is selected you need to pass down a callback function from MainTable
to RenderedTableRow
that when called, sets the dialog to be opened and stores which row was selected:
export default class MainTable extends Component {
state = {
selectedRow: null,
}
handleSelectRow(rowIndex) {
this.setState({
selectedRow: rowIndex,
})
}
render() {
return (
<div>
<div>
<Subheader>Table</Subheader>
<Table
multiSelectable={true}
>
// ...
<TableBody
deselectOnClickaway={false}
stripedRows
>
{rows.map((row, index) => (
<RenderedTableRow
row={row}
{...this.props}
onSelectRow={() => this.handleSelectRow(index)}
/>
))}
</TableBody>
</Table>
</div>
// Dialog goes here and is only rendered once per table
// it is only open when there is a row selected
<Dialog
open={Boolean(this.state.selectedRow)}
>
// you can get the selected row with rows[this.state.selectedRow]
</Dialog>
</div>
)
}
}
Upvotes: 2