Material-ui: Colunas classificáveis ​​na tabela

Criado em 6 ago. 2015  ·  71Comentários  ·  Fonte: mui-org/material-ui

Gostaria que houvesse colunas classificáveis ​​para a tabela de dados semelhantes às mostradas no google material design: http://www.google.com/design/spec/components/data-tables.html#data -tables-interaction

enhancement

Comentários muito úteis

Algo parecido

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

Todos 71 comentários

Se você precisar disso imediatamente, você pode tentar trabalhar com a tabela do branch v0.11 e criar seu próprio cabeçalho de coluna e usar eventos de clique/toque para determinar como classificar as linhas da tabela (ascendente/descendente). Estou com um pouco de fluxo/refluxo/redux kick e gostaria de ver a lógica de classificação fora do MUI. Não deve ser difícil fornecer os indicadores de classificação de cabeçalho de coluna e expor retornos de chamada para esses eventos. Ficaríamos felizes em aceitar um PR (contra o branch v0.11) se você tiver algum tempo.

Alguém trabalhando nisso? Nossa equipe (@VladimirPal) adoraria tentar, usando a abordagem de @jkruder .

Acho que só precisamos de sortIndicator e onClick para TableHeaderColumn , sortIndicator pode ser qualquer componente (geralmente um FontIcon ) que o usuário passe. A lógica de classificação seria tratada fora de Table .

A lógica de classificação pode ser complicada, por exemplo, classificação de coluna única, classificação de várias colunas e a ordem do ciclo de classificação ( Asc->Desc->None ou Desc->Asc ) e prioridade de coluna na classificação de várias colunas. Talvez seja melhor manter o Table do MUI enxuto e remover a lógica. Seu pensamento? @jkruder @sjstebbins

@zachguo Definitivamente concordo em manter a lógica de classificação fora da tabela. Eu consideraria uma matriz de classificação padrão de ['asc', 'desc', 'none'] e cada clique no cabeçalho da coluna progrediria no índice que retornaria a 0 e faria uma chamada para um CB com o valor de classificação e o nome/número/identificador da coluna. Esta matriz pode ser fornecida como um suporte para valores personalizados.

A classificação de várias colunas pode ser tratada pelo consumidor da tabela. Eu vi prioridade dada à ordem em que as colunas são clicadas. Pode ser mantido pelo consumidor como um array de objetos: [{columnId: 'asc'}, {otherColumnId: 'desc'}] . Eu adicionaria um campo multiColumnSortable (sinta-se à vontade para alterar o nome) para controlar a classificação de várias colunas.

@jkruder TBH Não tenho certeza se é uma boa ideia salvar uma matriz de classificação em Table . Estou pensando em uma abordagem de nível inferior, fazendo indicator e onClick desacoplados da lógica de classificação, para que

  • O componente Table seria usado puramente para renderização (portanto, mais fácil de raciocinar),
  • ambos os adereços podem atender a outras necessidades, como dar um emblema aos cabeçalhos das colunas ou destacar uma coluna clicando em seu cabeçalho.

Mas os contras são que as APIs não seriam muito úteis para usuários comuns.

@zachguo Bom ponto sobre o indicador; tudo sobre a dissociação da interface do usuário. Podemos fazer como você sugere e criar uma versão sem opinião da tabela com indicator e onClick e, em seguida, criar uma tabela classificável nos documentos para demonstrar a API. Na pior das hipóteses, podemos fornecer um componente SortableTable se descobrirmos que os usuários não estão achando a API intuitiva.

Sim, SortableTable é uma boa ideia.

Usaremos icon em vez de indicator conforme mostrado nas especificações do DataTable do MD :
screen shot 2015-08-25 at 6 37 45 pm
screen shot 2015-08-25 at 6 38 48 pm

@zachguo qual é o status disso?

@VladimirPal desenvolveu um que suporta classificação e paginação, sem alterar uma única linha de códigos MUI. Vamos testá-lo e portá-lo aqui quando acharmos que está pronto.

Eu adoraria ver isso, acabei de atualizar para 0.11 para jogar com as mesas

Bom trabalho

@zachguo ia começar a construir uma tabela classificável e paginável, mas veja se você tem algo funcionando. Quando você acha que vai ser bom ir? Feliz em usar algo enquanto isso fora do tronco material-ui.

@zachguo @VladimirPal alguma atualização sobre o status disso?

@shaurya947 @daniel-sim @sjstebbins

Fizemos a classificação (classificação de coluna única e de várias colunas) e paginação no lado do servidor e no lado do cliente, mas achamos difícil refatorar essas novas funcionalidades no MUI como APIs fáceis de usar sem perder a composibilidade que o MUI atualmente tem.

Pode-se realmente implementar classificação e paginação compondo os componentes da tabela do MUI sem escrever muitos códigos. A idéia geral é acompanhar os dados/classificação/página atuais por conta própria e deixar os componentes da tabela do MUI renderizá-los puramente.

IMHO, em vez de fornecer APIs de alto nível, como classificação e paginação, manter as APIs atuais de baixo nível é o caminho a seguir. Menos sobrecarga, mais fácil de compor. No entanto, para facilitar a classificação e paginação da renderização, podemos adicionar um prop icon / indicator e um evento onClick a TableHeaderColumn , e até mesmo um novo pré componente TableFooter com estilo.

Seus pensamentos?

cc: @oliviertassinari

@zachguo :

Pode-se realmente implementar classificação e paginação compondo os componentes da tabela do MUI sem escrever muitos códigos. A idéia geral é acompanhar os dados/classificação/página atuais por conta própria e deixar os componentes da tabela do MUI renderizá-los puramente.

Sim, isso é bastante factível.

Os adereços que você mencionou são de propósito geral e também podem ser úteis em outros cenários além da classificação. Então sinta-se à vontade para escrever um PR para isso ..

Isso pode não ser o ideal, mas consegui implementar a classificação apenas incluindo um div com um onClick dentro da TableHeaderColumn. Corrigir o comportamento onClick para TableHeaderColumn seria incrível, e IMO totalmente suficiente para 99% dos casos.

@roieki foi o que fizemos também

@shaurya947 @jkruder Na verdade, existe um suporte onClick para TableHeaderColumn , mas não está funcionando. relacionado #2011

Alguém quer assumir isso?

Algo parecido

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 bom exemplo, obrigado. Você poderia postar seu componente SmartTableRow também?

@chrisrittelmeyer claro, também atualizou o código da tabela acima

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 você tem esse código funcionando em qualquer lugar, talvez?

@chrisrittelmeyer pergunta estranha) com certeza eu tenho)

Oh! Eu deveria ter sido mais específico - você o tem em um ambiente ao qual você pode nos vincular? O código acima ainda está faltando algumas dependências, então, em vez de colar todas as partes aqui, pode ser mais fácil apenas me apontar para um repositório.

@vorlov Esta é uma implementação muito boa da Smart Table, mas não entendo muito bem a paginação. Não vejo nenhuma lógica para lidar com o deslocamento, limite e total antes da renderização?

@JK82 Depende de você, não estou usando paginação no meu projeto, adicionei para implementação futura.
Isso pode ser feito em componentWillReceiveProps ou componentWillMount

@vorlov Sweet, novamente, este é um trabalho muito bom

@vorlov hmm essa abordagem afeta diretamente os adereços (armazenamento redux) por referência :\

@NeXTs o que você quer dizer?

@vorlov
const sortedData = data.sort((a, b) => sortFunc(a, b, column));
data.sort - sort é mutator então modifica o campo data que é referência direta a this.props.data , não é?

Qual foi o motivo de fazer?

this.setState({
   data: sortedData
})

se render não usa dados de state , usa apenas const { data } = this.props;
talvez a ideia fosse pegar data dos adereços, cloná-lo, armazená-lo no estado e depois classificar/reverter data no estado?

TableSortLabel foi adicionado em next para ajudar com isso. Há também uma demonstração no branch next que tem colunas classificadas.

@NeXTs Na verdade, não sei onde você viu a loja redux lá. Eu passo dados no novo objeto para que não afete o armazenamento redux.

Props geralmente vem da loja redux, esse foi o meu ponto
Ok, esqueça, não é tão importante agora, obrigado de qualquer maneira! :+1:

@NeXTs Eu passo props para a tabela usando a propagação de objetos, então o estado não é alterado.
<SmartTable { ...{ tableHeaders, data, limit: 20, total: !!data && data.length, offset: 0, onPageClick: this.handleLoad } } />

@vorlov Entendi
@nathanmarks que legal! Quando estará disponível no branch master?

uma dependência está faltando no acima
importar formatTableCell de './formatTableCell';

@jimgong92 @chrisrittelmeyer formatoTableCell arquivo

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

Obrigado pela atualização.

@vorlov seu componente é ótimo, você deve colocá-lo dentro de um gist ou um exemplo de repositório

@nathanmarks onde está o link para TableSortLabel e a demonstração dele no ramo next ?

Sim, não há vestígios de TableSortLabel ou qualquer classificação no repositório. Querendo saber por que esta questão foi encerrada. Obrigado @vorlov pelo seu ótimo trabalho.

Sim, não há vestígios de TableSortLabel ou qualquer classificação no repositório.

Está na ramificação next : demos/tables/EnhancedTable .

@oliviertassinari Eu tentei seu exemplo, mas recebo este erro:

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

Está na próxima ramificação: demos/tables/EnhancedTable.

@oliviertassinari Desculpe incomodar. Parece que quando executo npm install material-ui@next para instalar o pacote de pré-lançamento no qual esta EnhancedTable existe, o componente TableSortLabel está faltando na pasta material-ui-build resultante. Estou perdendo um passo crítico aqui? Desde já, obrigado.

@GarrettVD ~O branch next ainda não foi lançado, então você terá que instalar o npm do github.~

Edit: desde então, lançamos um alpha inicial.

@mbrookes Ahh, entendi. Obrigado.

Ótimo trabalho, muito interessado neste recurso. Obrigado

@oliviertassinari é classificável e paginação no próximo ramo também? Quando essa ramificação será pública ou incorporada à atual? Muitas coisas interessantes são mantidas lá :)

@damianobarbati Lançamos um alpha inicial: npm install material-ui@next

para quem procura os documentos alfa sobre isso: https://material-ui-1dab0.firebaseapp.com/#/component -demos/tables

oi, novato fazendo check-in. tabelas de classificação seria adorável! quando isso será incorporado à versão de produção estável do MUI?

Aguardando essa versão para poder classificar os dados na minha tabela. Ótimo trabalho!

Oi,

Vejo que há uma nova versão, mas não consigo encontrar o recurso de classificação no código-fonte. Estou esquecendo de algo?

@nshung Você precisará usar material-ui@next

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

Isso é ótimo. Vejo classificação dinâmica na próxima demonstração. Você acha que faria sentido adicionar suporte para filtragem dinâmica aqui? Isso está além do escopo deste componente? Seria melhor implementado fora do MUI?

@lrettig A classificação dinâmica foi implementada no espaço do usuário, acho que devemos fazer o mesmo para a filtragem dinâmica. Por quê? Porque é muito mais fácil cobrir 80% dos casos de uso com 20% do esforço dessa maneira. Aprendemos com o branch master que as pessoas têm uma grande variedade de casos de uso com as tabelas.

Oi @oliviertassinari obrigado pelo feedback. Estou um pouco confuso, no entanto - a classificação não foi adicionada aqui ao próximo ramo? Então, isso não vai torná-lo mestre no futuro? Desculpe se estou perdendo algo óbvio.

@lrettig Certo, o próximo branch acabará sendo mesclado no master.

ansioso para que isso vá ao vivo.

@oliviertassinari -- Obrigado! Mas ainda estou confuso sobre seu comentário anterior. Você sugeriu que:

A classificação dinâmica foi implementada no espaço do usuário, acho que devemos fazer o mesmo para a filtragem dinâmica

Mas se a classificação está aqui, na próxima ramificação, e vai para o mestre, isso não significa que _não_ está no espaço do usuário? Estou esquecendo de algo?

Obrigado novamente.

@lrettig Por espaço do usuário, quero dizer que ele pode ser implementado fora do Material-UI. Isso foi feito na demonstração do branch next/v1-alpha.

Vejo que está fechado. Isso está disponível em uma versão que não seja material-ui@next ? Eu gostaria de bloquear uma versão por enquanto, se possível.

@jspengeman @next é um alias, pois estamos falando que está segmentando v1.0.0-alpha.21 .

@oliviertassinari obrigado! Eu entendi isso, acabei de ver que isso foi aberto em 2015 e queria saber se a funcionalidade chegou a uma versão 0.X no momento. Supondo que a resposta seja não?

Como essa funcionalidade chegou ao lançamento do material-ui?

@oliviertassinari como vai?

O recurso não chegou ao Material-UI. No entanto, demonstramos como implementá-lo na documentação da v1 .

@oliviertassinari obrigado pela resposta rápida!

Esta página foi útil?
0 / 5 - 0 avaliações