Jo Ko
Jo Ko

Reputation: 7575

ReactJS + Material-UI: How to use Material-UI’s FlatButton and Dialog in each TableRow?

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

Answers (3)

szymonm
szymonm

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:

enter image description here

Few remarks:

  • you should split this code into smaller components, I wrote this all in one to make it easy to test it,
  • remember to use 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

Danny
Danny

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

PhilippSpo
PhilippSpo

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

Related Questions