An open API service indexing awesome lists of open source software.

https://github.com/reactabular/easy

Easy, opinionated wrapper for Reactabular (MIT)
https://github.com/reactabular/easy

Last synced: 9 months ago
JSON representation

Easy, opinionated wrapper for Reactabular (MIT)

Awesome Lists containing this project

README

          

[![build status](https://secure.travis-ci.org/reactabular/easy.svg)](http://travis-ci.org/reactabular/easy) [![bitHound Score](https://www.bithound.io/github/reactabular/easy/badges/score.svg)](https://www.bithound.io/github/reactabular/easy) [![codecov](https://codecov.io/gh/reactabular/easy/branch/master/graph/badge.svg)](https://codecov.io/gh/reactabular/easy)

# reactabular-easy - Easy, opinionated wrapper for Reactabular

Given Reactabular is flexible by design, it's not the easiest to use and you may have to do quite a bit of wiring to make it work the way you want. *reactabular-easy* has been designed to make using it easier. It is opinionated and takes away some power. But on the plus side it allows you to render a fully featured table faster.

## How to Use?

To make the drag and drop functionality work, you have to set up [react-dnd-html5-backend](https://www.npmjs.com/package/react-dnd-html5-backend) or some other React DnD backend.

> You can find suggested default styling for the package at `style.css` in the package root.

> If you want to use the drag and drop functionality, you have to set up React DnD!

```jsx
/*
import React from 'react';
import * as resolve from 'table-resolver';
import * as dnd from 'reactabular-dnd';
import * as easy from 'reactabular-easy';
import VisibilityToggles from 'react-visibility-toggles';
import * as resizable from 'reactabular-resizable';
import * as tree from 'treetabular';
import * as search from 'searchtabular';
import HTML5Backend from 'react-dnd-html5-backend';
import { DragDropContext } from 'react-dnd';
import { compose } from 'redux';
import uuid from 'uuid';
import cloneDeep from 'lodash/cloneDeep';
import findIndex from 'lodash/findIndex';

import {
generateParents, generateRows
} from './helpers';
*/

const schema = {
type: 'object',
properties: {
Id: {
type: 'string'
},
fullName: {
$ref: '#/definitions/fullName'
},
company: {
type: 'string'
},
age: {
type: 'integer'
},
boss: {
$ref: '#/definitions/boss'
}
},
required: ['Id', 'fullName', 'company', 'age', 'boss'],
definitions: {
boss: {
type: 'object',
properties: {
name: {
type: 'string'
}
},
required: ['name']
},
fullName: {
type: 'object',
properties: {
first: {
type: 'string'
},
last: {
type: 'string'
}
},
required: ['first', 'last']
}
}
};
const rows = generateParents(generateRows(200, schema), 'Id');

class EasyDemo extends React.Component {
constructor(props) {
super(props);

this.state = {
rows,
columns: this.getColumns(),
sortingColumns: {},
query: {},
hiddenColumns: {} // : - show all by default
};
this.table = null;

this.onMoveRow = this.onMoveRow.bind(this);
this.onDragColumn = this.onDragColumn.bind(this);
this.onMoveColumns = this.onMoveColumns.bind(this);
this.onToggleColumn = this.onToggleColumn.bind(this);
this.onSelectRow = this.onSelectRow.bind(this);
this.onRemove = this.onRemove.bind(this);
this.onSort = this.onSort.bind(this);
this.onToggleShowingChildren = this.onToggleShowingChildren.bind(this);
}
componentWillMount() {
this.resizableHelper = resizable.helper({
globalId: uuid.v4(),
getId: ({ id }) => id
});
}
componentWillUnmount() {
this.resizableHelper.cleanup();
}
getColumns() {
return [
{
id: 'demo', // Unique ids for handling visibility checks
header: {
label: 'Demo',
draggable: true,
resizable: true,
formatters: [
() => Testing
]
},
cell: {
formatters: [
() => Demo,
],
toggleChildren: true
},
width: 100
},
{
id: 'name',
header: {
label: 'Name'
// Disabled for now as this is a difficult case in React DnD
//draggable: true
},
children: [
{
id: 'firstName',
property: 'fullName.first',
header: {
label: 'First Name',
draggable: true,
sortable: true,
resizable: true
},
cell: {
highlight: true
},
width: 125
},
{
id: 'lastName',
property: 'fullName.last',
header: {
label: 'Last Name',
draggable: true,
sortable: true,
resizable: true
},
cell: {
highlight: true
},
width: 125
}
]
},
{
id: 'age',
property: 'age',
header: {
label: 'Age',
draggable: true,
sortable: true,
resizable: true
},
cell: {
highlight: true
},
width: 150
},
{
id: 'company',
property: 'company',
header: {
label: 'Company',
draggable: true,
sortable: true,
resizable: true
},
cell: {
highlight: true
},
width: 250
},
{
id: 'bossName',
property: 'boss.name',
header: {
label: 'Boss',
draggable: true,
sortable: true,
resizable: true
},
cell: {
highlight: true
},
width: 200
},
{
cell: {
formatters: [
(value, { rowData }) => (


alert(`${JSON.stringify(rowData, null, 2)}`)}
/>
this.onRemove(rowData.Id)}
style={{ marginLeft: '1em', cursor: 'pointer' }}
>



)
]
},
width: 200
}
];
}
render() {
const {
searchColumn, columns, sortingColumns, rows, query, hiddenColumns
} = this.state;
const idField = 'Id';
const parentField = 'parent';

const visibleColumns = compose(
// 5. Patch columns with classNames for resizing
this.resizableHelper.initialize,
// 4. Bind columns (extra functionality)
easy.bindColumns({
toggleChildrenProps: { className: 'toggle-children' },
sortingColumns,
rows,
idField,
parentField,
props: this.props,

// Handlers
onMoveColumns: this.onMoveColumns,
onSort: this.onSort,
onDragColumnStart: (width, extra) => console.log('drag column start', width, extra),
onDragColumn: this.onDragColumn,
onDragColumnEnd: (width, extra) => console.log('drag column end', width, extra),
onToggleShowingChildren: this.onToggleShowingChildren
}),
// 3. Filter based on visibility again (children level)
columns => columns.filter(column => !hiddenColumns[column.id]),
// 2. Unpack
tree.unpack(),
// 1. Filter based on visibility (root level)
columns => columns.filter(column => !hiddenColumns[column.id])
)(columns);
const columnChildren = visibleColumns.filter(column => !column._isParent);
const headerRows = resolve.headerRows({
columns: tree.pack()(visibleColumns)
});

return (


!hiddenColumns[id]}
/>


Scroll to index:

this.table.tableBody.scrollTo(e.target.value)}
/>


Search
this.setState({ searchColumn })}
onChange={query => this.setState({ query })}
/>

{
this.table = table
}}
rows={rows}
headerRows={headerRows}
rowKey="Id"
sortingColumns={sortingColumns}
tableWidth={800}
tableHeight={400}
columns={columnChildren}
query={query}
classNames={{
table: {
wrapper: 'pure-table pure-table-striped'
}
}}
headerExtra={
this.setState({ query })}
/>
}

idField={idField}
parentField={parentField}

onMoveRow={this.onMoveRow}
onSelectRow={this.onSelectRow}
/>


);
}
onMoveRow({ sourceRowId, targetRowId }) {
const rows = tree.moveRows({
operation: dnd.moveRows({
sourceRowId,
targetRowId,
idField: 'Id'
}),
idField: 'Id', // Defaults to id
parentField: 'parent'
})(this.state.rows);

rows && this.setState({ rows });
}
onDragColumn(width, { column }) {
this.resizableHelper.update({
column,
width
});
}
onMoveColumns({ sourceLabel, targetLabel }) {
const columns = tree.unpack()(this.state.columns);

const sourceIndex = findIndex(
columns,
{ header: { label: sourceLabel } }
);
const targetIndex = findIndex(
columns,
{ header: { label: targetLabel } }
);

if (sourceIndex < 0 || targetIndex < 0) {
return null;
}

let source = columns[sourceIndex];
let target = columns[targetIndex];

if (source._isParent) {
return console.warn(
'Dragging parents is not supported yet'
);
}

// If source doesn't have a parent, make sure we are dragging to
// target parent by modifying the original structure.
if (!source.parent) {
const targetParents = tree.getParents({
index: findIndex(columns, { id: target.id })
})(columns);

// If trying to drag to a child, drag to its root
// parent instead.
if (targetParents.length) {
return console.warn(
'Dragging to a nested column is not supported yet'
);
}

const nestedSourceIndex = findIndex(
this.state.columns,
{ header: { label: sourceLabel } }
);
source = this.state.columns[nestedSourceIndex];

const nestedTargetIndex = findIndex(
this.state.columns,
{ header: { label: target.header.label } }
);
target = this.state.columns[nestedTargetIndex];

if (nestedSourceIndex < 0 || nestedTargetIndex < 0) {
return null;
}

// We are operating at root level now so move accordingly.
const movedColumns = dnd.move(
this.state.columns, nestedSourceIndex, nestedTargetIndex
);

// Retain widths while moving
movedColumns[nestedSourceIndex].width = source.width;
movedColumns[nestedTargetIndex].width = target.width;

this.setState({
columns: movedColumns
});
} else if (source.parent === target.parent) {
// Dragging within children now. This has to be against flattened data.
const movedColumns = dnd.move(columns, sourceIndex, targetIndex);

// Retain widths while moving.
// XXX: This works only for single level nesting.
const sourceWidth = source.width;
const targetWidth = target.width;

source.width = targetWidth;
target.width = sourceWidth;

this.setState({
columns: tree.pack()(movedColumns)
});
}
// If trying to drag from children to other children or so, do nothing.
}
onSelectRow({ selectedRowId, selectedRow }) {
console.log('onSelectRow', selectedRowId, selectedRow);
}
onSort(sortingColumns) {
console.log('onSort', sortingColumns);

this.setState({ sortingColumns });
}
onToggleColumn({ column }) {
const { hiddenColumns } = this.state;

this.setState({
hiddenColumns: {
...hiddenColumns,
[column.id]: !hiddenColumns[column.id]
}
});
}
onRemove(id) {
const rows = cloneDeep(this.state.rows);
const idx = findIndex(rows, { Id });

// this could go through flux etc.
rows.splice(idx, 1);

this.setState({ rows });
}
onToggleShowingChildren(rowIndex) {
const rows = cloneDeep(this.state.rows);

rows[rowIndex].showingChildren = !rows[rowIndex].showingChildren;

this.setState({ rows });
//},
}
}

// Set up drag and drop context
// const DragAndDropDemo = DragDropContext(HTML5Backend)(EasyDemo);

/*
export default EasyDemo
*/
```

## Styling

It is possible to pass custom `classNames` and `props` as listed below:

```js
classNames: {
table: null,
header: {
wrapper: null
// TODO
/*
row: null,
cell: null
*/
},
body: {
wrapper: null
// TODO
/*
row: null,
cell: null
*/
}
},
props: {
resize: {
container: {
style: {
color: 'red'
}
},
value: {},
handle: {}
},
sort: {
container: {},
value: {},
order: {}
}
}
```

For more control, you can also override `components` and also inject styling and class names through the column definition and the `onRow` handler.

## Customization

If you want to customize resize/sort controls further (add `className`s etc.), pass `props` like this:

```javascript
props: {
resize: {
container: {},
value: {},
handle: {}
},
sort: {
container: {},
value: {},
order: {}
}
},
```

## License

MIT.