admin管理员组

文章数量:1323524

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.

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.

Share Improve this question edited Jan 18, 2017 at 19:30 Jo Ko asked Jan 18, 2017 at 4:54 Jo KoJo Ko 7,57516 gold badges69 silver badges128 bronze badges 9
  • 1 What will be displayed in the dialog? For each row you're generating a new Dialog - is it necessary? As you mentioned, it will be very slow. Better approach is to have one Dialog element in the main table ponent and pass the necessary props. – szymonm Commented Jan 18, 2017 at 7:41
  • I'd move the dialog outside of the table, pass a callback to a button in the table row that on click, opens dialog and pass the selected row to it – Mateusz Commented Jan 18, 2017 at 7:51
  • @szymonm Different content for each row, but it will be displayed by using <Tabs> inside the Dialog but currently the Tabs don't work. The reason why I did it for each row is because each dialog is represented differently for every row. How can I get around that? – Jo Ko Commented Jan 18, 2017 at 19:37
  • @Mateusz Do you mind showing as answer for clarification? So I can accept and upvote it as well. – Jo Ko Commented Jan 18, 2017 at 19:38
  • @philippspo updated his answer and it's exactly what you need there ;-) – szymonm Commented Jan 19, 2017 at 7:37
 |  Show 4 more ments

3 Answers 3

Reset to default 2

You should probably only have one dialog for the whole table that lives in your MainTable ponent. 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>
    )
  }
}

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;

As I mentioned earlier in the ments, you should have only one Dialog element along with the table ponent. 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:

  • you should split this code into smaller ponents, 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).

本文标签: javascriptReactJSMaterialUI How to use MaterialUI’s FlatButton and Dialog in each TableRowStack Overflow