ReactJS + Material-UI:如何在每个 TableRow 中使用 Material-UI 的 FlatButton 和 Dialog?

Posted

技术标签:

【中文标题】ReactJS + Material-UI:如何在每个 TableRow 中使用 Material-UI 的 FlatButton 和 Dialog?【英文标题】:ReactJS + Material-UI: How to use Material-UI’s FlatButton and Dialog in each TableRow? 【发布时间】:2017-06-02 08:54:35 【问题描述】:

我有一个 Material-UI 的<Table>,并且在每个<TableRow>(动态渲染)中为<TableBody>,我想为其中一列设置一个按钮(<FlatButton>)。一旦点击按钮,它会打开一个<Dialog>,在里面会有一个工作的<Tabs>

那么,如何为特定列的每一行显示<FlatButton>,并在单击按钮时,在内部显示<Dialog> 以及工作中的<Tabs> 作为内容?并在外部单击时关闭<Dialog>

到目前为止,我有以下问题,但遇到了以下问题:打开但速度很慢,在 <Dialog> 之外单击并没有关闭它,<Tabs> 可见但不起作用:

主表:

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>
    )
  

渲染的表格行:

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>
    )
  

提前感谢您,我们会接受/支持答案。

【问题讨论】:

对话框中会显示什么?对于每一行,您正在生成一个新对话框 - 是否有必要?正如你提到的,它会很慢。更好的方法是在主表格组件中有一个 Dialog 元素并传递必要的道具。 我会将对话框移到表格之外,将回调传递给表格行中的按钮,单击该按钮,打开对话框并将所选行传递给它 @szymonm 每行内容不同,但会在对话框中使用&lt;Tabs&gt; 显示,但目前选项卡不起作用。我对每一行都这样做的原因是因为每一行的每个对话框都以不同的方式表示。我怎样才能解决这个问题? @Mateusz 您介意显示作为澄清的答案吗?所以我也可以接受并投票。 @philippspo 更新了他的答案,这正是您所需要的 ;-) 【参考方案1】:

您的MainTable 组件中的整个表格应该只有一个对话框。这更有效,因为您不需要每行一个对话框,而只需要一个对话框。

为了让RenderedTableRow 中的按钮打开模式并告诉它选择了哪一行,您需要将一个回调函数从MainTable 传递给RenderedTableRow,当调用该函数时,设置要打开的对话框并存储选择了哪一行:

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>
    )
  

【讨论】:

为了有动态多行,我将用一个数组填充表格,其中每一行都有特定的属性,如下所示:&lt;TableBody&gt;array.map(row =&gt; return(&lt;RenderedTableRow/&gt;));&lt;/TableBody&gt;。如果是这种情况,我将如何区分单击了哪一行并在单击的特定行的对话框中显示该特定内容?因为每一行都会在一个对话框中显示不同的内容。还有为什么&lt;Tabs&gt; 不起作用? 我扩展了代码示例以包含多行。不知道为什么Tabs 不起作用。他们在我看来很好。我什至尝试了你的Dialog 代码和Tabs 为我工作。 你介意用 codepen 或类似的东西来展示它吗?似乎没有工作.. :( 刚刚签到看看你是否看过我之前的评论。请告诉我。【参考方案2】:

下面是一个工作示例,它应该直接通过复制意大利面工作。

您的问题的答案是您需要能够区分不同的rows,将其设置为true 将显示所有对话框,或者可能只显示最后一个。一旦你区分它,显示你想要的dialog 应该不是问题。有很多方法可以只拥有一个 dialog 并且仍然可以进行这项工作,但我会让你弄清楚。

需要注意的是,您绝对可以清理此代码。为创建 TableRows TableColumns 等创建单独的文件。

我现在把它放在两列,但是你应该能够理解代码。如有任何其他问题,请随时提出。

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;

【讨论】:

为了有动态多行,我将用一个数组填充表格,其中每一行都有特定的属性,如下所示:array.map(row => return());.如果是这种情况,我将如何区分单击了哪一行并在单击的特定行的对话框中显示该特定内容?因为每一行都会在一个对话框中显示不同的内容。另外为什么 不起作用?如果你能展示一个实际工作的,那真的很有帮助。 我现在正在工作,但我会在今晚晚些时候更新答案以显示工作 tabs。我把那部分省略了。 顺便说一句,你试过上面的代码吗?单击不同的rows 会显示不同类型的数据。我唯一没有研究的是tabs,我不确定他们是做什么的,但我可以稍后查看文档。 给你@JoKo。这是一个带有标签的完整工作示例。希望对您有所帮助。【参考方案3】:

正如我之前在 cmets 中提到的,您应该只有一个 Dialog 元素和表格组件。在每一行中嵌入Dialog 会影响性能,通常是一种不好的做法。这是模拟标签的解决方案:

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;

现在是这样工作的:

几点说明:

您应该将此代码拆分为更小的组件,我将其全部编写在一个组件中以便于测试, 记得在列表的所有元素中使用 key 属性(迭代列表的位置) - 行和选项卡(您的问题中缺少 key)。

【讨论】:

以上是关于ReactJS + Material-UI:如何在每个 TableRow 中使用 Material-UI 的 FlatButton 和 Dialog?的主要内容,如果未能解决你的问题,请参考以下文章

ReactJS + Material-UI:如何在 Material-UI <Table/> 的 <TableRow/> 之间交替颜色?

ReactJS + Material-UI:如何减小 Material-UI 的 <TableRow/> 的列宽?

ReactJS + Material-UI:如何使用 Material-UI 的 <DatePicker> 将当前日期设置为默认值?

数据变化时如何在ReactJS中刷新Material-Table(Material-ui)组件

如何结合 ReactJs Router Link 和 material-ui 组件(如按钮)?

ReactJS with Material-UI:如何按字母顺序对 Material-UI 的 <TableRow> 数组进行排序?