0.1.7 • Published 3 years ago

react-flexi-table v0.1.7

Weekly downloads
5
License
MIT
Repository
github
Last release
3 years ago

⚠️ Deprecated ⚠️

This repository is no longer supported, please consider using alternatives, like react-data-grid or React Table.

FlexiTable (react-flexi-table)

FlexiTable is a react table component that dynamically adjust column widths to fit content and space created by the people at lendinvest.com.

Contents:

  • Why create this project?
  • High level explanation
  • Usage
    • Code
    • Guide to styling
  • How does it work
  • Missing Features
  • Known Issues
  • FAQ
  • Alternatives
  • Instalation
  • Developing & Contributing

Why?!

With most basic tables you have the following options:

  • Make every column a fixed with. This isn't reactive, is a pain to maintain, and can leave a lot of space unused (for instance when you have to leave enough space to fit $10,000,00 when 99% of times the amount is < $100)
  • Make every column a percentage. This is reactive to a degree, but will leave a lot of white space on larger devices, and squash everything on smaller devices. It's difficult to create a consistent apperance.

We wanted something better. We wanted the column widths to react to their content.

High level explanation

To make the table and data best fit the space, this component needs to know how each column works relative to it's data. FlexiTable supports three different column types to do this.

  • Fixed: The simplest column type. A single fixed width. Good for columns with data that is always the same on every row. e.g. an image, a 'Delete' button, or 'edit' link.
  • Fit To Content: This column type changes the width of the column to fit the biggest piece of content within that content. Good for things like status codes, or numbers where you don't want to take up any more space than you need to, but the content should never wrap.
  • Flexi: Flexi columns will expand or shrink to fit the available space. These are good for long pieces of text such as comments, addresses, and generally things that you are ok with wrapping over multiple lines. At least one column should be a flexi column. This is the default.

Usage

Code

Please see /demo/src/index.js for an example.

FlexiTable

You can define a felxitable in jsx as follows.

    <FlexiTable
      className="DemoTable"
      columns={tableColumns}
      data={tableData}
      halfGutterWidth={25}
      marginWidth={20}
      rowLimit={50}
    />

All the props with a * are required.

PropDescription
classNameThis is will be displayed on a higher level than the FlexiTable and FlexiTable--outer-box divs
columns*An array of objects, each describing a column, in column order. If this changes the table column sizing will be recalculated. See directly below for more info.
data*An array of objects, each item representing a row of data to display. There's no restrictions here, except that your column definitions know how to handle them. If this changes the table column sizing will be recalculated.
halfGutterWidth*Size in px of half a gutter. The inside padding for each cell in other words. The distance between two cells will be halfGutterWidth * 2.
marginWidth*Size in px of the margin to the left and right of the table.
rowLimitThe table will only show and calculate for the given amount of rows.

Note that halfGutterWidth and marginWidth are just defining horizontal width. Vertical height should be defined with css styles, that will be covered in the Guide to styling section. We needed to define the horizontal gutters and margins here so the column calculations correctly know how much free space to allocate.

Column Definitions

Here's a simple Fixed width style column:

    {
      // name is used for the css class
      name: 'Region',
      
      // fixedWidth indicates this is a fixed width type of column and what that width is
      fixedWidth: 120,
      
      // headerComponent is any react component (inline jsx, function, class, etc)
      // we'll use it to display the contents of the header cell
      headerComponent: () => <span>Region</span>,
      
      // cellComponent is any react component (inline jsx, function, class, etc)
      // we'll use it to display the contents for this column on each row
      cellComponent: props => <span>{props.rowData.region}</span>,
    },

Here's a Fit To Content style column, with some additional fields:

    {
      name: 'LastPaymentAmount',
      
      // fitToContentWidth indicates that this is a 'Fit to Content' type of
      // content. It will try to expand to fit the widest piece of content
      fitToContentWidth: true,
      headerComponent: () => <span>Last Payment Amount</span>,
      
      // cellDataSelector can either be a string referencing the data in the
      // current row object, or a function that returns the data to use for 
      // this cell
      cellDataSelector: 'subscription.lastPaymentAmount',
      
      // here we've used a predefined component for the cellComponent
      cellComponent: MyCustomFlexiTableCurrencyComponent,
      
      // fitToContentWidth columns will obey minWidth and maxWidth 
      // restrictions. These are in pixels
      minWidth: 70,
      maxWidth: 250,
    },

Here's a Flexi style column, with some additional fields:

    {
      name: 'Status',
      headerComponent: () => <span>Status</span>,
      cellDataSelector: 'subscription.status',
      
      // we can define a function to derive a css class to be used on the
      // outer-cell for styling.
      cellClassNameSelector: props => props.cellData.x === 'Up to date' ? 'good' : 'bad',
      
      // Note: In this case we haven't defined a cellComponent. It will
      // display the data from cellDataSelector alone within the cell by
      // default.
      
      // Note: We haven't set `fixedWidth` or `fitToContentWidth`. That
      // means this will be Flexi style column, since that's the default
    },

All the props with a * are required.

Either cellDataSelector or cellComponent, or both should be defined.

PropDescription
name*This is used for class names and identification.
headerComponent*A react component which will displays the header for the column. It will receive props: { columnName, columnNumber, column }
cellDataSelectorA string referencing the path of the data in the row (used by lodash.get), or a function to generate the cellData props used later. The function will receive arguments (rowData, rowNumber, columnName, columnNumber, column ). If this prop is not set, the cellData prop for other functions will be set to the rowData
cellComponentA react component which will displays the contents of the cell for the column. It will receive props: { cellData, rowNumber, rowData, columnName, columnNumber, column}. If this prop is not set a FlexiTableDefaultCellComponent will be used, which just renders the cellData as is.
cellClassNameSelectorA function that generating a classname to use for a cell. Usefull for status type columns where you might want to set the background or some feature of the cell via css. It will receive the same props as cellComponent, { cellData, rowNumber, rowData, columnName, columnNumber, column}
minWidthAn integer that sets the minimum width for a column. Has no affect on fixedWidth columns.
maxWidthAn integer that sets the maximum width for a column. Has no affect on fixedWidth columns.
fixedWidthAn integer that sets the fixed width for a column, and sets the column to fixed mode for column width calculations.
fitToContentWidthA boolean that defines if a column should size itself to fit the maximum content width of any of its cells. Sets the column to Fit to Content mode for column width calcuations. minWidth & maxWidth can be used to restrict this. If this is false or not set, and fixedWidth is not set, the the column will go into Flexi mode for width calculations.

Note the columnNumber and rowNumber are 0-indexed.

For the props/args that are passed to cellDataSelector, cellComponent & cellClassNameSelector:

PropDescription
cellDataThis is the result of the cellDataSelector, or the rowData if cellDataSelector is not defined.
rowNumber0-indexed number of the row (from top to bottom).
rowDataThe current element of the data prop passed to FlexiTable for the current row.
columnNameName field of the current column definition.
columnNumber0-indexed number of the column (from left to right).
columnThe current column definition.

You can add additional props to the column definitions which will be passed through to the column. For example sortOrder if you want to display a sort indicator on the column. To prevent any conflicts with future fields that may be added to the column definition, add them to a custom field on your column.

Guide to styling

Please see /demo/src/demo.scss for a full example.

Specific Styling Examples:

Spacing above & below row

There are currently two primary classes for setting the spacing above and below rows: FlexiTableSectionVSpace and FlexiTableRowVSpace.

FlexiTableSectionVSpace will appear above the first row of a section and below the last row of a section, where a section is the header or body. Additionally there is FlexiTableSectionVSpace--edge-top and FlexiTableSectionVSpace--edge-bottom if you want to get more specifc. So if you wanted to make the top of the header at 50px and set the rest at 15px you could do the following:

.FlexiTableSectionVSpace {
    height: 15px;
}
.FlexiTable--header .FlexiTableSectionVSpace.FlexiTableSectionVSpace--edge-top {
    height: 50px;
}

FlexiTableRowVSpace is very similar, except it's applied to the spaces inbetween normal rows. It also has FlexiTableRowVSpace--edge-top and FlexiTableRowVSpace--edge-bottom if you wanted to set the space above a row seperatly to the space below.

To just set a space of 15px around all types of rows you can do the following:

.FlexiTableSectionVSpace,
.FlexiTableRowVSpace {
    height: 15px;
}

Border around the table

To set a border around the whole table apply a style to .FlexiTable--border-box. DO NOT set a border style to the .FlexiTable--outer-box or .FlexiTable the border box switches around depending on whether or not a scroll-bar is being displayed

.FlexiTable--border-box {
    border: 1px solid #d6d6d6;
}

Alternating background colors for rows (zebra-striping)

Alternating background colors can be done by using subselectors on FlexiTableBodyRow as follows:

.FlexiTableBodyRow:nth-child(even) {
    background-color: rgba(234,234,234,0.1);
}

.FlexiTableBodyRow:nth-child(odd) {
  background-color: rgba(234,234,234,0.4);
}

Note that header row is FlexiTableHeaderRow, different separate from FlexiTableBodyRow, and they both share FlexiTableRow.

Aligning column content

Assuming you have a column with name: 'Region' you could right align it by doing the following:

.FlexiTable--column-Region  .FlexiTableCell {
  text-align: right;
}

This aligns both the header and body cells. If you just wanted to align the header or body use .FlexiTableHeaderCell or .FlexiTableBodyCell respectively, instead of .FlexiTableCell

Heading styles

The following sets a seperate background color, border and text weight for the headings (This is using sass nesting):

.FlexiTableHeaderRow {
  background-color: rgba(234,234,234,0.1);
  border-bottom: 1px solid #d6d6d6;

  .FlexiTableCell {
    font-weight: 600;
  }
}

Note you could have used .FlexiTableHeaderCell in this case. It doesn't matter.

Different font-sizes for body columns

The following sets a specific font size for the 'Region' column. Only on body cells, not header cells. Using .FlexiTableCell instead of .FlexiTableBodyCell would set it on both header and body cell.

.FlexiTable--column-Region .FlexiTableBodyCell {
  font-size: 17px;
  line-height: 26px;
}

Overflow ellipsis

If you wish to display ellipsis instead of wrapping text that no longer fits in the column you can do the following. Note this requires that the contents of the text is within a span. Using the default cellComponent won't have a span.

.FlexiTable--column-Region  .FlexiTableBodyCell span {
  overflow: hidden;
  text-overflow: ellipsis;
  display: inherit;
  white-space: nowrap;
}

General div/class layout:

  • Your className is at the top level div.
  • Underneath that is FlexiTable--outer-box.
  • At the next level we have a div with both FlexiTable and FlexiTable-[uuid] where the uuid is auto generated.
  • Under that we split things into FlexiTable--header and FlexiTable--body
    • FlexiTable--header is broken up into one or more FlexiTableHeaderRow (which are also FlexiTableRow)
    • FlexiTableHeaderRow has divs for each column with all three FlexiTableHeaderCell--outer, FlexiTable--column-num-[columnNumber], FlexiTable--column-[columnName] classes
    • FlexiTableHeaderCell--outer contains the spacers described below, then a div with both FlexiTableHeaderCell and FlexiTableCell
    • Then at the next level we have the custom headerComponent defined in the column.
  • For the FlexiTable--bodyFlexiTable--bodyis broken up into one or moreFlexiTableBodyRow(which are alsoFlexiTableRow`)
    • FlexiTableBodyRow has divs for each column with all three of FlexiTableBodyCell--outer, FlexiTable--column-num-[columnNumber], FlexiTable--column-[columnName] classes
    • FlexiTableBodyCell--outer contains the spacers described below, then a div with both FlexiTableBodyCell and FlexiTableCell
    • Then at the next level we have the custom cellComponent defined in the column.

Between the FlexiTableRow and the FlexiTableCell for both header and body rows we have the following spacer code that is responsible for the margin/padding around each cell.

  • First we have a FlexiTableSectionVSpace or FlexiTableRowVSpace depending on the row position. This div creates the vertical space above each cell.
  • In the middle we have FlexiTableCellSpacer--inner-row div, which contains:
    • Firstly a FlexiTableColumnMargin or FlexiTableColumnGutter depending on the column position. This div creates the horizontal space to the left of the cell.
    • And in the middle we have FlexiTableCell itself.
    • Lastly a FlexiTableColumnMargin or FlexiTableColumnGutter depending on the column position. This div creates the horizontal space to the right of the cell.
  • Lastly we have a FlexiTableSectionVSpace or FlexiTableRowVSpace depending on the row position. This div creates the vertical space below each cell.

The spacing example Spacing above & below row a few sections above describes how you can use those spacers.

Styling Restrictions

The following styles and attributes are managed by the FlexiTable itself. Changing them could break things. This is mostly related to column width calculations

  • minWidth, maxWidth and width on anything within . If you want to set the width of the table constrain it by setting the width on a div that sourounds the .
  • FlexiTable--measure-mode. This is used to figure out the width of the columns. Messing with this is going to break the calculations.
  • The box-sizing, display, flex, overflow attributes. Changing any of these will seriously break the table.
  • Ignoring that go crazy. And anything within the components you define with headerComponent and cellComponent are fair game.

How does it work

Before displaying anything the table does an invisible test render, with every cell able to strech as wide as they can. At that point we measure the widths of each of the cells at their widest point, and feed that information, along with the column types, to a calculator.

If the data or column definitions change, we do the test render again to get the new widths.

If the size of the table changes we just recalculate the space.

FlexTable uses FlexBox to present the table.

A custom uuid is created for each table so if there is multiple FlexiTables on on page, it doesn't get confused about which table it needs to calculate and manipulate styles for.

What doesn't flexi table do?

  • Row or Cell click event handlers - This is something we really should add. It should be fairly straight forward. You can of course add click handlers to anything in the components you define for each cell.
  • Sorting, filtering, pagination & loading data - FlexiTable is only concerned with displaying data. All sorting, filtering, fetching and manipulating of data should be done elsewhere. Having said that it's easy enough to add sorting indicators if that's what you'd like. Please see the information in the styling section.
  • Cells that span multiple columns - This is something I'd like to add.
  • Cells that span multiple rows - We have NO plans to do this currently. It's not something we think we'll need.
  • Aggregate rows (GroupBy headers + Footers) - We would be interested in adding this, but have no immediate plans to.
  • Fixed Table Height with vertical scroll - We have no current plans to do this, but it would be a good feature to have.

Dont's!

  • DON'T filter or map the rows in your render method. FlexiTable doesn't do a deep comparison, and will think the data has changed every render. It will recalculate the size of the table, killing performance.
  • DON'T filter or map the columns in your render method. FlexiTable doesn't do a deep comparison, and will think the column definitions have changed every render. It will recalculate the size of the table, killing performance.
  • DON'T put the FlexiTable in a container that grows fit it's contents. The flexi table will try to grow to fit it's container, and if the container is growing with it the FlexiTable won't stop growing until it reaches all of its column's maxWidths.

Known Issues:

  • When the FlexiTable does a test render to gather the width of the each cell it will render every cell in the table. If the table has 50,000 rows, it will render all of them. We should limit this by default to say 50 rows, and provide an option to set that number.
  • It does not support iOS < 10.0
  • FullStory (and probably other simiar session-replay tools) will not display this table correctly. Everything will be squashed to the left. That's because they don't support changing the stylesheet using the insertRule method of CSSStyleSheet.

Q & A

Q: What happens if, with min-widths and fixed-widths set, the table is too wide to fit within the page?

A: The table will become a scrollable div, letting the user scroll horizontally.

Q: What happens if, with max-widths and fixed-widths set, the table is too narrow to fit within the page?

A: The div with class 'FlexiTable' will shrink to fit the space. Note that the div with the className you provided, and the div with class 'FlexiTable--outer-box' will still be remain at the full width. Use this to position the table within the extra space.

Q: Why does each cell have seperate divs for margins/padding? Couldn't you just using the margin/padding css attributes?

A: Good question. There are a few reasons.

  • I wanted to give people the ability to adjust their own padding/margins within cells without breaking the gutters.
  • It makes it easier to define spacing via styles. i.e. for the first or last row of the table. This way you can just set the height for say the .FlexiTable--body .FlexiTableSectionVSpace--edge-top to increase the distance between the header and first body row.
  • It lets you define things in the margins with css.
  • In the initial version of this table the gutters could become narrower as the table started hitting the min-width limits of its columns. With more advanced column types this wasn't needed so much, but it's something I do consider adding back.

Dependencies

ReactTable uses a number of open source projects to work properly:

The demo has a few more random dependencies.

Support

Tested against the latest versions of: Chrome, Firefox, Safari & (iOS 10+), Edge and IE 11.

Installation

Add your package from npm:

> yarn add react-flexi-table

FlexiTable uses sass for it's basic styling. If your using webpack you'll need to add 'node-sass' and 'sass-loader' to your package dev-dependencies.

Then for Webpack 1 you'll want to include the loader as part of your webpack build under 'module/loaders'.

  module: {
    ...
    loaders: [
      ...
      {
        test: /\.css$/,
        loader: 'style!css?importLoaders=1!postcss'
      },
      {
        test: /\.scss$/,
        loaders: ["style", "css", "sass"]
      },

Or for Webpack 2 you'll want to include the loader as part of your webpack build under 'module/rules'.

  module: {
    rules: [{
      test: /\.scss$/,
      use: [{
        loader: "style-loader" // creates style nodes from JS strings
      }, {
        loader: "css-loader" // translates CSS into CommonJS
      }, {
        loader: "sass-loader" // compiles Sass to CSS
      }]
    }]
  }

Finally after doing a yarn install (or npm install) you can load the project via the package.

import FlexiTable from 'react-flexi-table'

Development

Want to contribute? Great!

Flexitables uses nwb for fast developing.

Open your favorite Terminal and run this to install nwb.

$ npm install -g nwb

Next install the dependencies:

$ npm install

Now you can start the demo, build the library excetera. Type the following to start the demo:

$ npm start

To build the library do the following:

$ npm build
$ cp src/FlexiTable.scss lib/
$ cp src/FlexiTable.scss es/

In the future we expect to make the build and publish process automated.

License

MIT