Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sortable Columns in Table #1352

Closed
sjstebbins opened this issue Aug 6, 2015 · 71 comments
Closed

Sortable Columns in Table #1352

sjstebbins opened this issue Aug 6, 2015 · 71 comments
Labels
new feature New feature or request

Comments

@sjstebbins
Copy link
Contributor

I would like there to be sortable columns for the data table similar to those shown in google material design: http://www.google.com/design/spec/components/data-tables.html#data-tables-interaction

@jkruder
Copy link
Contributor

jkruder commented Aug 7, 2015

If you need this immediately, you can try working with the table from branch v0.11 and create your own column header and use click/tap events to determine how to sort the table rows (ascending/descending). I'm on a bit of flux/reflux/redux kick and would like to see the sorting logic outside of MUI. It should not be difficult to provide the column header sort indicators and expose callbacks for those events. We'd be happy to accept a PR (against branch v0.11) if you have some time.

@zachguo
Copy link
Contributor

zachguo commented Aug 25, 2015

Anybody working on this? Our team(@VladimirPal) would love to give it a shot, using @jkruder 's approach.

@zachguo
Copy link
Contributor

zachguo commented Aug 25, 2015

I think we just need sortIndicator and onClick for TableHeaderColumn, sortIndicator can be whatever component(usually a FontIcon) user passes in. The sorting logic would be handled outside of Table.

@zachguo
Copy link
Contributor

zachguo commented Aug 25, 2015

Sorting logic can be complicated, e.g. single column sorting, multi-column sorting, and the order of sorting cycle(Asc->Desc->None or Desc->Asc), and column priority in multi-column sorting. Maybe it's better to keep MUI's Table lean and move the logic out. Your thought? @jkruder @sjstebbins

@jkruder
Copy link
Contributor

jkruder commented Aug 26, 2015

@zachguo Definitely agree with keeping the sorting logic outside of the table. I would consider a default sort array of ['asc', 'desc', 'none'] and every click on the column header would progress the index which will wrap back to 0 and make a call to a CB with the sort value and column name/number/identifier. This array could be supplied as a prop for custom values.

Multi column sorting can be handled by the consumer of the table. I've seen priority given to the order the columns are clicked. Could be maintained by the consumer as an array of objects: [{columnId: 'asc'}, {otherColumnId: 'desc'}]. I would add a multiColumnSortable (feel free to change the name) field to control multiple column sorting.

@zachguo
Copy link
Contributor

zachguo commented Aug 26, 2015

@jkruder TBH I'm not sure whether it's a good idea to save a sort array into Table. I'm thinking of a lower-level approach, by making both indicator and onClick decoupled from sorting logic, so that

  • Table component would be used purely for rendering (hence easier to reason about),
  • both props can serve other needs like giving a badge to column headers, or highlighting a column by clicking its header.

But cons are that APIs would not be very handy for common users.

@jkruder
Copy link
Contributor

jkruder commented Aug 26, 2015

@zachguo Good point about the indicator; all about decoupling the UI. We could do as you suggest and create an unopinionated version of the table with an indicator and onClick and then create a sortable table in the docs to demonstrate the API. Worst case, we can provide a SortableTable component if we find that the users are not finding the API intuitive.

@zachguo
Copy link
Contributor

zachguo commented Aug 26, 2015

Yup, SortableTable is a good idea.

@zachguo
Copy link
Contributor

zachguo commented Aug 26, 2015

We'll use icon instead of indicator as shown in In MD's DataTable specs:
screen shot 2015-08-25 at 6 37 45 pm
screen shot 2015-08-25 at 6 38 48 pm

@sjstebbins
Copy link
Contributor Author

@zachguo whats the status of this?

@zachguo
Copy link
Contributor

zachguo commented Aug 28, 2015

@VladimirPal has developed one which supports both sorting and pagination, without changing single line of MUI codes. We'll test it out and port it here when we think it's ready.

@CumpsD
Copy link
Contributor

CumpsD commented Aug 28, 2015

I would love to see this, just upgraded to 0.11 to play with the tables

Nice work

@daniel-sim
Copy link

@zachguo was going to start building a sortable, pageable table myself but see you've got something working. When do you think it'll be good to go? Happy to use something in the meantime outside of the material-ui trunk.

@shaurya947
Copy link
Contributor

@zachguo @VladimirPal any updates on the status of this?

@zachguo
Copy link
Contributor

zachguo commented Nov 24, 2015

@shaurya947 @daniel-sim @sjstebbins

We did sorting(both single-column and multi-column sorting) and pagination on both server-side and client-side, but found it hard to refactor these new functionalities into MUI as easy-to-use APIs without losing composibility that MUI currently has.

One can actually implement sorting & pagination by composing MUI's table components without writing too many codes. The general idea is to keep track of current data/sort/page by yourself, and let MUI's table components purely render them.

IMHO, instead of providing high-level APIs such as sorting and pagination, keeping current low-level APIs is the way to go. Less overhead, easier to compose. However, to make rendering sorting and pagination easier, we may add an icon/indicator prop and an onClick event to TableHeaderColumn, and even a new pre-styled TableFooter component.

Your thoughts?

@shaurya947
Copy link
Contributor

cc: @oliviertassinari

@zachguo:

One can actually implement sorting & pagination by composing MUI's table components without writing too many codes. The general idea is to keep track of current data/sort/page by yourself, and let MUI's table components purely render them.

Yes, that is quite doable.

The props that you mentioned are rather general purpose and could also come in handy in other scenarios besides sorting. So feel free to write up a PR for that..

@rschwabco
Copy link

This might not be ideal, but I was able to implement sorting by just including a div with an onClick inside the TableHeaderColumn. Fixing the onClick behavior for TableHeaderColumn would be awesome, and IMO totally enough for 99% of the cases.

@zachguo
Copy link
Contributor

zachguo commented Nov 30, 2015

@roieki this is what we did too

@shaurya947 @jkruder There's actually an onClick prop for TableHeaderColumn but it's not working. related #2011

@shaurya947
Copy link
Contributor

Does anybody want to take that up?

@alitaheri alitaheri added the new feature New feature or request label Dec 8, 2015
@vorlov
Copy link

vorlov commented May 13, 2016

Something Like That

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;
  }
}

@chrisrittelmeyer
Copy link

@vorlov nice example, thanks. Could you post your SmartTableRow component as well?

@vorlov
Copy link

vorlov commented May 20, 2016

@chrisrittelmeyer sure, also updated the code for table above

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;

@chrisrittelmeyer
Copy link

@vorlov do you have this code up and working anywhere, perhaps?

@vorlov
Copy link

vorlov commented May 20, 2016

@chrisrittelmeyer stange question) sure I have)

@chrisrittelmeyer
Copy link

Oh! I should have been more specific - do you have it in an environment that you can link us to? The above code is still missing some dependencies, so rather than pasting all the parts here, it might be easier to just point me to a repo.

@JK82
Copy link

JK82 commented Jul 5, 2016

@vorlov This is a really nice implementation of the Smart Table, I don't quite understand the pagination however. I don't see any logic to handle the offset, limit, and total before render?

@vorlov
Copy link

vorlov commented Jul 5, 2016

@JK82 It's up to you, I am not using pagination on my project, added it for future implementation.
It could be done in componentWillReceiveProps or componentWillMount

@JK82
Copy link

JK82 commented Jul 5, 2016

@vorlov Sweet, again this is really nice work

@remon-nashid
Copy link

Yup there are no traces of TableSortLabel or any sortability in the repo. Wondering why this issue was closed. Thanks @vorlov for your great work, though.

@oliviertassinari
Copy link
Member

oliviertassinari commented Nov 19, 2016

Yup there are no traces of TableSortLabel or any sortability in the repo.

It's on the next branch: demos/tables/EnhancedTable.

@acpower7
Copy link

acpower7 commented Nov 20, 2016

@oliviertassinari I tried your example however I get this error:

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

@GarrettVD
Copy link

GarrettVD commented Jan 9, 2017

It's on the next branch: demos/tables/EnhancedTable.

@oliviertassinari Sorry to bother. It seems that when I run npm install material-ui@next in order to install the pre-release package in which this EnhancedTable exists, the TableSortLabel component is missing from the resulting material-ui-build folder. Am I missing a critical step here? Thanks in advance.

@mbrookes
Copy link
Member

mbrookes commented Jan 9, 2017

@GarrettVD The next branch isn't released yet, so you will have to npm install from github.

Edit: we have since released an early alpha.

@GarrettVD
Copy link

@mbrookes Ahh, gotcha. Thanks.

@leialexisjiang
Copy link

Great job, very interested in this feature. Thanks

@damianobarbati
Copy link

@oliviertassinari is sortable and pagination on next branch as well? When is that branch going to be public or merged into current one? Lots of interesting stuff is kept in there :)

@mbrookes
Copy link
Member

@damianobarbati We've released an early alpha: npm install material-ui@next

@doaboa
Copy link
Contributor

doaboa commented Feb 14, 2017

for anyone else looking for the alpha docs on this: https://material-ui-1dab0.firebaseapp.com/#/component-demos/tables

@swyxio
Copy link

swyxio commented Mar 22, 2017

hi, newbie checking in. sorting tables would be lovely! when will this be merged into the stable production version of MUI?

@brangnu
Copy link

brangnu commented Mar 28, 2017

Waiting for this release to be able to sort data in my table. Great work!

@brangnu
Copy link

brangnu commented Apr 11, 2017

Hi,

I see there is a new release, but I can't seem to find the sorting feature in the source code. Am I missing something?

@mbrookes
Copy link
Member

mbrookes commented Apr 11, 2017

@nshung You will need to use material-ui@next

https://material-ui-1dab0.firebaseapp.com/component-demos/tables

@lrettig
Copy link

lrettig commented Jun 24, 2017

This is great. I see dynamic sorting in the next demo. Do you think it would make sense to add support for dynamic filtering here? Is that beyond the scope of this component? Would it be better implemented outside MUI?

@oliviertassinari
Copy link
Member

@lrettig Dynamic sorting was implemented in user space, I think that we should do the same for dynamic filtering. Why? Because it's much easier to cover 80% of the use cases with 20% of the effort that way. We have learned with the master branch that people have a very wide variety of use cases with the tables.

@lrettig
Copy link

lrettig commented Jun 24, 2017

Hi @oliviertassinari thanks for the feedback. I'm a little confused, though--wasn't sorting added here to the next branch? So won't that make it to master in the future? Sorry if I'm missing something obvious.

@oliviertassinari
Copy link
Member

@lrettig Right, the next branch will eventually going to be merged into the master one.

@swyxio
Copy link

swyxio commented Jun 26, 2017

looking forward for this to go live.

@lrettig
Copy link

lrettig commented Jun 28, 2017

@oliviertassinari -- Thanks! But I'm still confused about your previous comment. You suggested that:

Dynamic sorting was implemented in user space, I think that we should do the same for dynamic filtering

But if sorting is here, in the next branch, and will go to master, doesn't that mean it's not in user space? Am I missing something?

Thanks again.

@oliviertassinari
Copy link
Member

oliviertassinari commented Jun 28, 2017

@lrettig By user space, I mean that it can be implemented outside of Material-UI. That what have been done on the next/v1-alpha branch demo.

@jspengeman
Copy link

I see this is closed. Is this available in a release that is not material-ui@next? I would like to lock down a version for the time being if possible.

@oliviertassinari
Copy link
Member

oliviertassinari commented Jul 6, 2017

@jspengeman @next is an alias, as we are speaking it's targeting v1.0.0-alpha.21.

@jspengeman
Copy link

@oliviertassinari thanks! I understood as much, just saw this was opened in 2015 and was wondering the functionality has made it into a 0.X release at the point. Assuming the answer is no?

@csells
Copy link

csells commented Jul 16, 2017

How this functionality made it into the release of material-ui?

@socksrust
Copy link

@oliviertassinari how is it going?

@oliviertassinari
Copy link
Member

The feature hasn't made it to Material-UI. However, we demonstrate how to implement it in the documentation of v1.

@socksrust
Copy link

@oliviertassinari thnx for ur fast reply!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new feature New feature or request
Projects
None yet
Development

No branches or pull requests