Material-ui: [Table] pagination / infinite scrolling

Created on 25 Aug 2015  路  28Comments  路  Source: mui-org/material-ui

@jkruder In addition to sorting, we would love to give this a shot, too. Do you have any suggestion about implementation?

Table enhancement

Most helpful comment

@kodermax logics for pagination can be handled by user outside MUI. For creating a table footer similar to the specs of material design you can try following code snippet.

// footer.jsx

import React from 'react';
import {TableFooter as TF, TableRow, TableRowColumn, FontIcon, IconButton} from 'material-ui';

const styles = {
  footerContent: {
    float: 'right'
  },
  footerText: {
    float: 'right',
    paddingTop: 16,
    height: 16
  }
};

const TableFooter = React.createClass({

  propTypes: {
    offset: React.PropTypes.number.isRequired, // current offset
    total: React.PropTypes.number.isRequired, // total number of rows
    limit: React.PropTypes.number.isRequired, // num of rows in each page
    onPageClick: React.PropTypes.func // what to do after clicking page number
  },

  render() {
    let offset = this.props.offset;
    let total = this.props.total;
    let limit = this.props.limit;
    return (
      <TF adjustForCheckbox={false}>
        <TableRow>
          <TableRowColumn style={styles.footerContent}>
            <IconButton disabled={offset === 0} onClick={this.props.onPageClick.bind(null, offset - limit)}>
              <FontIcon className="material-icons">chevron_left</FontIcon>
            </IconButton>
            <IconButton disabled={offset + limit >= total} onClick={this.props.onPageClick.bind(null, offset + limit)}>
              <FontIcon className="material-icons">chevron_right</FontIcon>
            </IconButton>
          </TableRowColumn>
          <TableRowColumn style={styles.footerText}>
            {Math.min((offset + 1), total) + '-' + Math.min((offset + limit), total) + ' of ' + total}
          </TableRowColumn>
        </TableRow>
      </TF>
    );
  }

});

export default TableFooter;

screen shot 2016-01-06 at 11 54 11 am

All 28 comments

If you're interested in infinite scrolling, I would take a look at: https://github.com/facebook/fixed-data-table. Might find some inspiration there.

If you want to pursue pagination I would suggest creating the pagination UI; next/prev buttons, page number buttons with an ellipsis and provide a callback that simply communicates what page was selected. This will allow something like redux to be utilized for the paging and then the consumer of the table can populate it with the new table rows. What do you think?

My rough idea is that the Table would detect and keep track of scrolling positions, and have a callback onScrollToEnd. Pagination logic(either client or server side) would be handled by user outside of Table. Pros and cons of this approach are similar to those of my approach for sorting.

The infinite scroll is best implemented when you have all of the data and can control the heights of each row. Currently, we do not enforce a specific row height so determining how many rows to insert at a given point becomes more complicated. My vote would be to keep the table simple and provide paging hooks -- or, create a decorator that will wrap a table and provide pagination controls. See https://www.google.com/design/spec/components/data-tables.html#data-tables-tables-within-cards for an example of the paging controls.

I see, thanks for sharing!

@jkruder @zachguo I have implemented infinite scrolling as a prop 'infiniteScroll' with true or false acceptance and 'infiniteScrollOffset' which accepts a number indicating at many pixels from the bottom of the table should more rows be loaded on scroll. I am passing mapped state items as children to the tableBody which creates the TableRows based on the 'page' set by scroll position, but I believe I can move all this logic to within the tableBody component itself. I will submit a PR for this soon. Thanks

You could also try https://github.com/orgsync/react-list for the infinit scroll.

When do you plan to do pagination?

@kodermax logics for pagination can be handled by user outside MUI. For creating a table footer similar to the specs of material design you can try following code snippet.

// footer.jsx

import React from 'react';
import {TableFooter as TF, TableRow, TableRowColumn, FontIcon, IconButton} from 'material-ui';

const styles = {
  footerContent: {
    float: 'right'
  },
  footerText: {
    float: 'right',
    paddingTop: 16,
    height: 16
  }
};

const TableFooter = React.createClass({

  propTypes: {
    offset: React.PropTypes.number.isRequired, // current offset
    total: React.PropTypes.number.isRequired, // total number of rows
    limit: React.PropTypes.number.isRequired, // num of rows in each page
    onPageClick: React.PropTypes.func // what to do after clicking page number
  },

  render() {
    let offset = this.props.offset;
    let total = this.props.total;
    let limit = this.props.limit;
    return (
      <TF adjustForCheckbox={false}>
        <TableRow>
          <TableRowColumn style={styles.footerContent}>
            <IconButton disabled={offset === 0} onClick={this.props.onPageClick.bind(null, offset - limit)}>
              <FontIcon className="material-icons">chevron_left</FontIcon>
            </IconButton>
            <IconButton disabled={offset + limit >= total} onClick={this.props.onPageClick.bind(null, offset + limit)}>
              <FontIcon className="material-icons">chevron_right</FontIcon>
            </IconButton>
          </TableRowColumn>
          <TableRowColumn style={styles.footerText}>
            {Math.min((offset + 1), total) + '-' + Math.min((offset + limit), total) + ' of ' + total}
          </TableRowColumn>
        </TableRow>
      </TF>
    );
  }

});

export default TableFooter;

screen shot 2016-01-06 at 11 54 11 am

@zachguo That looks good. Do you think we could add it to the lib?

I can do a PR if users find it helpful.

send PR, we will improve

I'd be interested in a pagination implementation. Been trying out the footer bit @zachguo posted and it looks good.

@zachguo since the changes in v15.0 pertaining to module imports, my module loading approach would not render the TableFooter component. I couldn't find a suitable workaround for that so instead I slightly modified the structure of your component, see below for anyone interested in using.

import Component from 'react-pure-render/component';
import React, { PropTypes } from 'react';
import ChevronLeft from 'material-ui/svg-icons/navigation/chevron-left';
import ChevronRight from 'material-ui/svg-icons/navigation/chevron-right';
import IconButton from 'material-ui/IconButton';

const styles = {
  footerContent: {
    float: 'right'
  },
  footerText: {
    float: 'right',
    paddingTop: '16px',
    height: '16px'
  }
};

class PagiFooter extends Component {

  constructor(props) {
    super(props);
  }

  static propTypes = {
    offset: PropTypes.number.isRequired, // current offset
    total: PropTypes.number.isRequired, // total number of rows
    limit: PropTypes.number.isRequired, // num of rows in each page
    onPageClick: PropTypes.func.isRequired // what to do after clicking page number
  }

  render() {

    let { offset, total, limit } = this.props;

    return (
        <div style={styles.footerContent}>
          <IconButton disabled={offset === 0} onClick={this.props.onPageClick.bind(null, offset - limit)}>
            <ChevronLeft/>
          </IconButton>
          <IconButton disabled={offset + limit >= total} onClick={this.props.onPageClick.bind(null, offset + limit)}>
            <ChevronRight/>
          </IconButton>
          {Math.min((offset + 1), total) + '-' + Math.min((offset + limit), total) + ' of ' + total}
        </div>
    );
  }

}

export default PagiFooter;

Including the component as so:

          <TableFooter>
            <TableRow>
              <TableRowColumn colSpan="3">
                <PagiFooter offset={1} total={2} limit={3} onPageClick={this.pagiFunction}/>
              </TableRowColumn>
            </TableRow>
          </TableFooter>

I couldn't determine what was causing your component not to render - I believe it might be some sort of namespacing collision with the module loading approach. I'd be curious to hear any possible culprits. Thanks anyway.

So based on implementation given by @zachguo I was able to add pagination and it works just fine. Thanks for your comments. Following is the complete implementation code (except for methods):

<Table
        height={this.state.height}
        fixedHeader={this.state.fixedHeader}
        fixedFooter={this.state.fixedFooter}
        selectable={false}
        multiSelectable={false}
      >
        <TableHeader displaySelectAll={false} adjustForCheckbox={false}>
          <TableRow>
            {this.tableData ? Object.keys(this.tableData[0]).map((key, idx) => (
              <TableHeaderColumn>{Utils.toHeaderCase(key)}</TableHeaderColumn>
            ))
              : ''
            }

          </TableRow>
        </TableHeader>
        <TableBody
          deselectOnClickaway={this.state.deselectOnClickaway}
          displayRowCheckbox={false}
          showRowHover={this.state.showRowHover}
          stripedRows={this.state.stripedRows}
        >
          {this.tableData ? this.tableData.map((row, index) => (
            <TableRow key={index} selected={row.selected}>
              {Object.keys(row).map((prop, ind) => (
                <TableRowColumn>{row[prop]}</TableRowColumn>
              ))
              }
            </TableRow>
          )) : ''}
        </TableBody>
        <TableFooter>
          <TableRow>
            <TableRowColumn style={styles.footerContent}>
              <IconButton onClick={() => this.getPreviousPage()} disabled={!(this.props.pagination.hasOwnProperty('prev'))}>
                <ChevronLeft/>
              </IconButton>
              <IconButton onClick={() => this.getNextPage()} disabled={!(this.props.pagination.hasOwnProperty('next'))}>
                <ChevronRight/>
              </IconButton>
            </TableRowColumn>
            <TableRowColumn style={styles.footerText} />
          </TableRow>
        </TableFooter>
      </Table>

@rs6g10 - that looks like a docs example in the making. Care to submit a PR?

@Dombo I upgraded MUI to v0.15 today and ran into same issue. I don't know what happened, thanks for your workaround.

@Dombo hmm, even your workaround didn't work for me. What I got is just an empty tr... PagiFooter was ignored and not rendered at all...

Actually no matter what I put into

<TableFooter adjustForCheckbox={false}>
  <TableRow>
    <TableRowColumn colSpan="3">
      ...
    </TableRowColumn>
  </TableRow>
</TableFooter>

got ignored.... Do you know why?

@zachguo I also struggled with that issue - my guess is that it's related to the TableRow component.

      var rowColumns = _react2.default.Children.map(this.props.children, function (child, columnNumber) {
        if (_react2.default.isValidElement(child)) {
          return _react2.default.cloneElement(child, {
            columnNumber: columnNumber,
            hoverable: _this2.props.hoverable,
            key: _this2.props.rowNumber + '-' + columnNumber,
            onClick: _this2.onCellClick,
            onHover: _this2.onCellHover,
            onHoverExit: _this2.onCellHoverExit,
            style: (0, _simpleAssign2.default)({}, styles.cell, child.props.style)
          });
        }
      });

      return _react2.default.createElement(
        'tr',
        _extends({
          className: className,
          style: prepareStyles((0, _simpleAssign2.default)(styles.root, style))
        }, other),
        rowColumns
      );

Specifically

 if (_react2.default.isValidElement(child)) {

Is my PagiFooter somehow not a valid react element?

@Dombo Thanks for your reply! I don't think that's the case, it didn't work even if I simply put a string inside TableRowColumn... 馃槙

@Dombo @zachguo i figured it out. When you create a custom component wrapping TableFooter, you need to

  1. Omit the inner TableFooter element
  2. Make the wrapping component extend React.Component
  3. And set the muiName property on the wrapped component:
class MyCustomTableFooter extends React.Component {
  render() {
    return (
      <TableRow>
        <TableRowColumn>
          <h1>This is my custom table footer</h1>
        </TableRowColumn>
      </TableRow>
    );
  }
}

MyCustomTableFooter.muiName = 'TableFooter';

@Dombo @zachguo
I think it is a bug caused by <TableFooter adjustForCheckbox={false}>
If adjustForCheckbox={false} is removed, the footer shows as usual.
Related Issues: https://github.com/callemall/material-ui/issues/4581

@zachguo In the next major version we'll be including a regular table and an example of a material styled infinite/virtualized data grid component.

@nathanhere and when you plan to release new major version?

@rs6g10 @Dombo
Nice example, It helped a lot!

Can you give an ideia how to brake the list in pages?

Thanks,

@Dombo Could you post your method to calculate the offset?

has anyone made any progress for infinite horizontal scroll?

I gonna close this, guys. Few takeaways from this thread:

  • logics for pagination and infinite scrolling should be handled by user/you not MUI
  • examples for TableFooter can be found above
  • "next major version we'll be including a regular table and an example of a material styled infinite/virtualized data grid component." https://github.com/callemall/material-ui/issues/1511#issuecomment-235240163

If you are open to design specs other than Material Design, take a look at Ant Design's table component.

Was this page helpful?
0 / 5 - 0 ratings