Material-ui: 表中的可排序列

创建于 2015-08-06  ·  71评论  ·  资料来源: mui-org/material-ui

我希望数据表有类似于谷歌材料设计中显示的可排序列: http ://www.google.com/design/spec/components/data-tables.html#data -tables-interaction

enhancement

最有用的评论

类似的东西

import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn, TableFooter } from 'material-ui/Table';
import { SmartTableRow } from 'components';
import React, { PropTypes, Component } from 'react';
import styles from './SmartTable.scss';
import SortIcon from 'material-ui/svg-icons/action/swap-vert';
import IconButton from 'material-ui/IconButton';
import ChevronLeft from 'material-ui/svg-icons/navigation/chevron-left';
import ChevronRight from 'material-ui/svg-icons/navigation/chevron-right';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

function sortFunc(a, b, key) {
  if (typeof(a[key]) === 'number') {
    return a[key] - b[key];
  }

  const ax = [];
  const bx = [];

  a[key].replace(/(\d+)|(\D+)/g, (_, $1, $2) => { ax.push([$1 || Infinity, $2 || '']); });
  b[key].replace(/(\d+)|(\D+)/g, (_, $1, $2) => { bx.push([$1 || Infinity, $2 || '']); });

  while (ax.length && bx.length) {
    const an = ax.shift();
    const bn = bx.shift();
    const nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
    if (nn) return nn;
  }

  return ax.length - bx.length;
}

class SmartTable extends Component {

  static childContextTypes = {
    muiTheme: React.PropTypes.object.isRequired
  }

  constructor(props, context) {
    super(props, context);
    this.state = { isAsc: false, sortHeader: null };
  }

  getChildContext() {
    return { muiTheme: getMuiTheme() };
  }

  sortByColumn(column, data) {
    const isAsc = this.state.sortHeader === column ? !this.state.isAsc : true;
    const sortedData = data.sort((a, b) => sortFunc(a, b, column));

    if (!isAsc) {
      sortedData.reverse();
    }

    this.setState({
      data: sortedData,
      sortHeader: column,
      isAsc
    });
  }

  render() {

    const { offset, limit, total, tableHeaders, data, onPageClick } = this.props;

    return (
      <Table className={ styles.table } selectable={false}>
        <TableHeader displaySelectAll ={false} adjustForCheckbox={false}>
          <TableRow>
            {!!tableHeaders && tableHeaders.map((header, index) => (
              <TableHeaderColumn key={index}>
                <div className={styles.rowAlign}>
                  {header.alias}
                  <SortIcon
                    id={header.dataAlias}
                    className={styles.sortIcon}
                    onMouseUp={(e) => this.sortByColumn(e.target.id, data) }
                  />
                </div>
              </TableHeaderColumn>
            ))}
          </TableRow>
        </TableHeader>
        <TableBody showRowHover stripedRows displayRowCheckbox={false}>
          {!!data && data.map((row, index) => (
            <SmartTableRow key={index} {...{ row, index, tableHeaders }} />
          ))}
        </TableBody>
        <TableFooter>
          <TableRow>
            <TableRowColumn>
                <div className={styles.footerControls}>
                  { `${Math.min((offset + 1), total)} - ${Math.min((offset + limit), total)} of ${total}` }
                  <IconButton disabled={offset === 0} onClick={onPageClick.bind(null, offset - limit)}>
                    <ChevronLeft/>
                  </IconButton>
                  <IconButton disabled={offset + limit >= total} onClick={onPageClick.bind(null, offset + limit)}>
                    <ChevronRight/>
                  </IconButton>
                </div>
              </TableRowColumn>
          </TableRow>
        </TableFooter>
      </Table>
    );
  }
}

SmartTable.propTypes = {
  tableHeaders: PropTypes.array,
  data: PropTypes.array,
  offset: PropTypes.number, // current offset
  total: PropTypes.number, // total number of rows
  limit: PropTypes.number, // num of rows in each page
  onPageClick: PropTypes.func // what to do after clicking page number
};

export default SmartTable;

.table {
  width: auto;
  padding-top: 30px;
}

.rowAlign {
  display: flex;
  align-items: center;
}

.footerControls {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

.sortIcon {
  cursor: pointer;
  path {
    fill: rgb(158, 158, 158) !important;
    pointer-events: none;
  }
}

所有71条评论

如果您立即需要,您可以尝试使用分支 v0.11 中的表格并创建自己的列标题并使用单击/点击事件来确定如何对表格行进行排序(升序/降序)。 我有点flux/reflux/redux,想看看MUI之外的排序逻辑。 提供列标题排序指示器并公开这些事件的回调应该不难。 如果您有时间,我们很乐意接受 PR(针对分支 v0.11)。

有人在做这个吗? 我们的团队(@VladimirPal)很乐意尝试使用@jkruder的方法。

我认为我们只需要sortIndicatoronClickTableHeaderColumnsortIndicator可以是用户传入的任何组件(通常是FontIcon )。排序逻辑将在Table之外处理。

排序逻辑可以很复杂,例如单列排序、多列排序、排序周期的顺序( Asc->Desc->NoneDesc->Asc ),以及多列排序中的列优先级。 也许最好保持 MUI 的Table精简并将逻辑移出。 你的想法? @jkruder @sjstebbins

@zachguo绝对同意将排序逻辑保留在表格之外。 我会考虑['asc', 'desc', 'none']的默认排序数组,每次单击列标题都会使索引返回 0,并使用排序值和列名/编号/标识符调用 CB。 该数组可以作为自定义值的道具提供。

多列排序可以由表的消费者处理。 我已经看到点击列的顺序优先。 可以由消费者维护为对象数组: [{columnId: 'asc'}, {otherColumnId: 'desc'}] 。 我会添加一个 multiColumnSortable(随意更改名称)字段来控制多列排序。

@jkruder TBH 我不确定将排序数组保存到Table是否是个好主意。 我正在考虑一种较低级别的方法,通过使indicatoronClick与排序逻辑解耦,这样

  • Table组件将纯粹用于渲染(因此更容易推理),
  • 这两个道具都可以满足其他需求,例如为列标题提供徽章,或通过单击列标题突出显示列。

但缺点是 API 对于普通用户来说不是很方便。

@zachguo关于指标的好点; 所有关于解耦 UI。 我们可以按照您的建议进行操作,并使用indicatoronClick创建一个无意见版本的表格,然后在文档中创建一个可排序的表格来演示 API。 最坏的情况是,如果我们发现用户没有找到直观的 API,我们可以提供一个SortableTable组件。

是的, SortableTable是个好主意。

我们将使用icon代替indicator ,如在 MD 的 DataTable 规范中所示:
screen shot 2015-08-25 at 6 37 45 pm
screen shot 2015-08-25 at 6 38 48 pm

@zachguo这是什么状态?

@VladimirPal开发了一种支持排序和分页的软件,无需更改单行 MUI 代码。 当我们认为它准备好时,我们将对其进行测试并将其移植到这里。

我很想看到这个,刚刚升级到 0.11 来玩桌子

干得好

@zachguo打算自己开始构建一个可排序的、可分页的表,但看到你有一些工作。 你觉得什么时候去比较好? 与此同时,很高兴在 material-ui 主干之外使用一些东西。

@zachguo @VladimirPal有关此状态的任何更新?

@shaurya947 @daniel-sim @sjstebbins

我们在服务器端和客户端都进行了排序(单列和多列排序)和分页,但发现很难将这些新功能重构为易于使用的 API,同时又不失 MUI 目前的可组合性已。

实际上可以通过组合MUI的表格组件来实现排序和分页,而无需编写太多代码。 总体思路是自己跟踪当前数据/排序/页面,让 MUI 的表格组件纯粹渲染它们。

恕我直言,与其提供排序和分页等高级 API,不如保留当前的低级 API。 更少的开销,更容易组合。 但是,为了使渲染排序和分页更容易,我们可以添加一个icon / indicator属性和一个onClick事件到TableHeaderColumn ,甚至是一个新的pre - 样式的TableFooter组件。

你的意见?

抄送: @oliviertassinari

@zachguo

实际上可以通过组合MUI的表格组件来实现排序和分页,而无需编写太多代码。 总体思路是自己跟踪当前数据/排序/页面,让 MUI 的表格组件纯粹渲染它们。

是的,这是完全可行的。

您提到的道具是相当通用的,除了排序之外,也可以在其他场景中派上用场。 所以请随意为此写一个 PR..

这可能并不理想,但我能够通过在 TableHeaderColumn 中包含一个带有 onClick 的 div 来实现排序。 修复 TableHeaderColumn 的 onClick 行为会很棒,而且 IMO 完全可以满足 99% 的情况。

@roieki这也是我们所做的

@shaurya947 @jkruder实际上有 $ TableHeaderColumnonClick道具,但它不起作用。 相关#2011

有人愿意接受吗?

类似的东西

import { Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn, TableFooter } from 'material-ui/Table';
import { SmartTableRow } from 'components';
import React, { PropTypes, Component } from 'react';
import styles from './SmartTable.scss';
import SortIcon from 'material-ui/svg-icons/action/swap-vert';
import IconButton from 'material-ui/IconButton';
import ChevronLeft from 'material-ui/svg-icons/navigation/chevron-left';
import ChevronRight from 'material-ui/svg-icons/navigation/chevron-right';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

function sortFunc(a, b, key) {
  if (typeof(a[key]) === 'number') {
    return a[key] - b[key];
  }

  const ax = [];
  const bx = [];

  a[key].replace(/(\d+)|(\D+)/g, (_, $1, $2) => { ax.push([$1 || Infinity, $2 || '']); });
  b[key].replace(/(\d+)|(\D+)/g, (_, $1, $2) => { bx.push([$1 || Infinity, $2 || '']); });

  while (ax.length && bx.length) {
    const an = ax.shift();
    const bn = bx.shift();
    const nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
    if (nn) return nn;
  }

  return ax.length - bx.length;
}

class SmartTable extends Component {

  static childContextTypes = {
    muiTheme: React.PropTypes.object.isRequired
  }

  constructor(props, context) {
    super(props, context);
    this.state = { isAsc: false, sortHeader: null };
  }

  getChildContext() {
    return { muiTheme: getMuiTheme() };
  }

  sortByColumn(column, data) {
    const isAsc = this.state.sortHeader === column ? !this.state.isAsc : true;
    const sortedData = data.sort((a, b) => sortFunc(a, b, column));

    if (!isAsc) {
      sortedData.reverse();
    }

    this.setState({
      data: sortedData,
      sortHeader: column,
      isAsc
    });
  }

  render() {

    const { offset, limit, total, tableHeaders, data, onPageClick } = this.props;

    return (
      <Table className={ styles.table } selectable={false}>
        <TableHeader displaySelectAll ={false} adjustForCheckbox={false}>
          <TableRow>
            {!!tableHeaders && tableHeaders.map((header, index) => (
              <TableHeaderColumn key={index}>
                <div className={styles.rowAlign}>
                  {header.alias}
                  <SortIcon
                    id={header.dataAlias}
                    className={styles.sortIcon}
                    onMouseUp={(e) => this.sortByColumn(e.target.id, data) }
                  />
                </div>
              </TableHeaderColumn>
            ))}
          </TableRow>
        </TableHeader>
        <TableBody showRowHover stripedRows displayRowCheckbox={false}>
          {!!data && data.map((row, index) => (
            <SmartTableRow key={index} {...{ row, index, tableHeaders }} />
          ))}
        </TableBody>
        <TableFooter>
          <TableRow>
            <TableRowColumn>
                <div className={styles.footerControls}>
                  { `${Math.min((offset + 1), total)} - ${Math.min((offset + limit), total)} of ${total}` }
                  <IconButton disabled={offset === 0} onClick={onPageClick.bind(null, offset - limit)}>
                    <ChevronLeft/>
                  </IconButton>
                  <IconButton disabled={offset + limit >= total} onClick={onPageClick.bind(null, offset + limit)}>
                    <ChevronRight/>
                  </IconButton>
                </div>
              </TableRowColumn>
          </TableRow>
        </TableFooter>
      </Table>
    );
  }
}

SmartTable.propTypes = {
  tableHeaders: PropTypes.array,
  data: PropTypes.array,
  offset: PropTypes.number, // current offset
  total: PropTypes.number, // total number of rows
  limit: PropTypes.number, // num of rows in each page
  onPageClick: PropTypes.func // what to do after clicking page number
};

export default SmartTable;

.table {
  width: auto;
  padding-top: 30px;
}

.rowAlign {
  display: flex;
  align-items: center;
}

.footerControls {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}

.sortIcon {
  cursor: pointer;
  path {
    fill: rgb(158, 158, 158) !important;
    pointer-events: none;
  }
}

@vorlov很好的例子,谢谢。 您也可以发布您的SmartTableRow组件吗?

@chrisrittelmeyer当然,还更新了上表的代码

import { TableRow, TableRowColumn } from 'material-ui/Table';
import React, { PropTypes } from 'react';
import formatTableCell from './formatTableCell';

const SmartTableRow = ({ index, row, tableHeaders }) => (
  <TableRow key={index}>
    {tableHeaders.map((header, propIndex) => (
      <TableRowColumn key={propIndex}>{formatTableCell(row[header.dataAlias], header.format)}</TableRowColumn>
    ))}
  </TableRow>
);

SmartTableRow.propTypes = {
  index: PropTypes.number,
  row: PropTypes.object
};

export default SmartTableRow;

@vorlov你有这个代码并且可以在任何地方工作吗?

@chrisrittelmeyer stange 问题)当然我有)

哦! 我应该更具体一点 - 您是否在可以将我们链接到的环境中拥有它? 上面的代码仍然缺少一些依赖项,因此与其在此处粘贴所有部分,不如将我指向一个 repo 可能更容易。

@vorlov这是智能表的一个非常好的实现,但是我不太了解分页。 我没有看到任何逻辑来处理渲染之前的偏移量、限制和总计?

@JK82由你决定,我没有在我的项目中使用分页,添加它以供将来实施。
它可以在 componentWillReceiveProps 或 componentWillMount 中完成

@vorlov Sweet,这又是一个非常好的工作

@vorlov嗯,这种方法通过引用直接影响道具(redux 存储):\

@NeXTs你是什么意思?

@vorlov
const sortedData = data.sort((a, b) => sortFunc(a, b, column));
data.sort - sort 是mutator所以它修改data直接引用this.props.data $ 的字段,不是吗?

这样做的原因是什么?

this.setState({
   data: sortedData
})

如果render不使用来自 state 的数据,它只使用const { data } = this.props;
也许想法是从道具中获取data ,克隆它,将其存储在 state 中,然后在 state 中排序/恢复data

TableSortLabel已添加到next以帮助解决此问题。 还有一个关于next分支的演示,它具有排序的列。

@NeXTs其实我不知道你在哪里看到 redux 商店。 我在新对象中传递数据,因此它不会影响 redux 存储。

道具通常来自 redux 商店,这是我的观点
好吧,没关系,现在没那么重要了,还是谢谢你! :+1:

@NeXTs我使用对象传播将道具传递给表格,因此状态不会发生变化。
<SmartTable { ...{ tableHeaders, data, limit: 20, total: !!data && data.length, offset: 0, onPageClick: this.handleLoad } } />

@vorlov知道了
@nathanmarks哦,酷! 什么时候可以在 master 分支中使用?

上面缺少一个依赖项
从'./formatTableCell'导入formatTableCell;

@jimgong92 @chrisrittelmeyer formatTableCell 文件

import numeral from 'numeral';
import React from 'react';
import { Link } from 'react-router';
import FlatButton from 'material-ui/FlatButton';

export default (cell, format, row) => {
  switch (format && format.type) {
    case 'link':
      return <Link to={ `${format.url}${row.id}` }>{ cell }</Link>;    
    case 'percentage':
      return `${cell}%`;    
    case 'money':
      return numeral(cell).format('0,0');
    default:
      return cell;
  }
};

感谢更新。

@vorlov 你的组件很棒,你应该把它放在 gist 或 repo 示例中

@nathanmarks TableSortLabel的链接和next分支上的演示在哪里?

是的,在 repo 中没有 TableSortLabel 的痕迹或任何可排序性。 想知道为什么这个问题被关闭了。 不过,感谢@vorlov的出色工作。

是的,在 repo 中没有 TableSortLabel 的痕迹或任何可排序性。

它在next分支上: demos/tables/EnhancedTable

@oliviertassinari我试过你的例子但是我得到这个错误:

TypeError: Cannot read property 'render' of undefined
EnhancedTable.render
http://localhost:8004/app.a3611250a45594961d8c.js:122073:47
http://localhost:8004/app.a3611250a45594961d8c.js:11761:22
measureLifeCyclePerf
http://localhost:8004/app.a3611250a45594961d8c.js:11040:13
ReactCompositeComponentWrapper._renderValidatedComponentWithoutOwnerOrContext
http://localhost:8004/app.a3611250a45594961d8c.js:11760:26
ReactCompositeComponentWrapper._renderValidatedComponent
http://localhost:8004/app.a3611250a45594961d8c.js:11787:33
ReactCompositeComponentWrapper.performInitialMount
http://localhost:8004/app.a3611250a45594961d8c.js:11327:31
ReactCompositeComponentWrapper.mountComponent
http://localhost:8004/app.a3611250a45594961d8c.js:11223:22
Object.mountComponent
http://localhost:8004/app.a3611250a45594961d8c.js:3816:36
ReactDOMComponent.mountChildren
http://localhost:8004/app.a3611250a45594961d8c.js:10366:45
ReactDOMComponent._createInitialChildren
http://localhost:8004/app.a3611250a45594961d8c.js:7453:33

它位于下一个分支:demos/tables/EnhancedTable。

@oliviertassinari抱歉打扰。 似乎当我运行npm install material-ui@next以安装此 EnhancedTable 所在的预发布包时,生成的 material-ui-build 文件夹中缺少 TableSortLabel 组件。 我在这里错过了关键步骤吗? 提前致谢。

@GarrettVD ~ next分支还没有发布,所以你必须从 github 安装 npm。~

编辑:我们已经发布了一个早期的 alpha。

@mbrookes啊,明白了。 谢谢。

干得好,对这个功能很感兴趣。 谢谢

@oliviertassinari在下一个分支上也可以排序和分页吗? 该分支何时公开或合并到当前分支? 里面有很多有趣的东西:)

@damianobarbati我们发布了早期的 alpha: npm install material-ui@next

对于其他正在寻找 alpha 文档的人: https: //material-ui-1dab0.firebaseapp.com/#/component -demos/tables

嗨,新手登记入住。分类表会很可爱! 什么时候将其合并到 MUI 的稳定生产版本中?

等待此版本能够对我的表中的数据进行排序。 做得好!

你好,

我看到有一个新版本,但我似乎无法在源代码中找到排序功能。 我错过了什么吗?

这很棒。 我在下一个演示中看到了动态排序。 您认为在此处添加对动态过滤的支持是否有意义? 这超出了这个组件的范围吗? 在 MUI 之外实施会更好吗?

@lrettig动态排序是在用户空间中实现的,我认为我们应该对动态过滤做同样的事情。 为什么? 因为用 20% 的努力覆盖 80% 的用例要容易得多。 我们从 master 分支了解到,人们对表格有各种各样的用例。

@oliviertassinari感谢您的反馈。 不过,我有点困惑——这里没有将排序添加到下一个分支吗? 那么这不会让它成为未来的主人吗? 抱歉,如果我遗漏了一些明显的东西。

@lrettig对,下一个分支最终将合并到主分支中。

期待这个上线。

@oliviertassinari——谢谢! 但我仍然对你之前的评论感到困惑。 你建议:

动态排序是在用户空间实现的,我认为我们应该对动态过滤做同样的事情

但是,如果排序在这里,在下一个分支中,并且将转到 master,这是否意味着它_not_ 在用户空间中? 我错过了什么吗?

再次感谢。

@lrettig通过用户空间,我的意思是它可以在 Material-UI 之外实现。 这就是在 next/v1-alpha 分支演示中所做的。

我看到这是关闭的。 这在不是material-ui@next的版本中可用吗? 如果可能的话,我想暂时锁定一个版本。

@jspengeman @next是一个别名,正如我们所说,它的目标是v1.0.0-alpha.21

@oliviertassinari谢谢! 我明白了,刚刚看到它是在 2015 年开放的,我想知道该功能是否已经成为 0.X 版本。 假设答案是否定的?

这个功能是如何进入material-ui的发布的?

@oliviertassinari怎么样了?

该功能尚未进入 Material-UI。 但是,我们在v1 的文档中演示了如何实现它。

@oliviertassinari 谢谢你的快速回复!

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

mattmiddlesworth picture mattmiddlesworth  ·  3评论

sys13 picture sys13  ·  3评论

ryanflorence picture ryanflorence  ·  3评论

ghost picture ghost  ·  3评论

iamzhouyi picture iamzhouyi  ·  3评论