UNPKG

10.4 kBMarkdownView Raw
1[![build status](https://secure.travis-ci.org/reactabular/treetabular.svg)](http://travis-ci.org/reactabular/treetabular) [![bitHound Score](https://www.bithound.io/github/reactabular/treetabular/badges/score.svg)](https://www.bithound.io/github/reactabular/treetabular) [![codecov](https://codecov.io/gh/reactabular/treetabular/branch/master/graph/badge.svg)](https://codecov.io/gh/reactabular/treetabular)
2
3# Treetabular - Tree utilities
4
5`treetabular` provides tree helpers for Reactabular. It allows you to set up collapsible rows that can contain more collapsible ones while remaining within a table format.
6
7To achieve this, `treetabular` relies on a flat structure that contains the hierarchy:
8
9```javascript
10const tree = [
11 {
12 _index: 0,
13 id: 123,
14 name: 'Demo'
15 },
16 {
17 _index: 1,
18 id: 456,
19 name: 'Another',
20 parent: 123
21 },
22 {
23 _index: 2,
24 id: 789,
25 name: 'Yet Another',
26 parent: 123
27 },
28 {
29 _index: 3,
30 id: 532,
31 name: 'Foobar'
32 }
33];
34```
35
36If there's a `parent` relation, the children must follow their parent right after it (you might use `fixOrder` helper function if your data does not meet that criteria).
37
38> You can find suggested default styling for the package at `style.css` in the package root.
39
40## API
41
42```javascript
43import * as tree from 'treetabular';
44
45// Or you can cherry-pick
46import { filter } from 'treetabular';
47import { filter as filterTree } from 'treetabular';
48```
49
50### Transformations
51
52**`tree.collapseAll = ({ property = 'showingChildren' }) => (rows) => [<collapsedRow>]`**
53
54Collapses rows by setting `showingChildren` of each row to `false`.
55
56**`tree.expandAll = ({ property = 'showingChildren' }) => (rows) => [<expandedRow>]`**
57
58Expands rows by setting `showingChildren` of each row to `true`.
59
60**`tree.filter = ({ fieldName, idField = 'id', parentField = 'parent' }) => (rows) => [<filteredRow>]`**
61
62Filters the given rows using `fieldName`. This is handy if you want only rows that are visible assuming visibility logic has been defined.
63
64### Queries
65
66**`tree.getLevel = ({ index, idField = 'parentId', parentField = 'parent' }) => (rows) => <level>`**
67
68Returns the nesting level of the row at the given `index` within `rows`.
69
70**`tree.getChildren = ({ index, idField = 'id', parentField = 'parent' }) => (rows) => [<child>]`**
71
72Returns children based on given `rows` and `index`. This includes children of children.
73
74**`tree.getImmediateChildren = ({ index, idField = 'id', parentField = 'parent' }) => (rows) => [<child>]`**
75
76Returns immediate children based on given `rows` and `index`.
77
78**`tree.getParents = ({ index, idField = 'parentId', parentField = 'parent' }) => (rows) => [<parent>]`**
79
80Returns parents based on given `rows` and `index`.
81
82**`tree.hasChildren = ({ index, idField = 'id', parentField = 'parent '}) => (rows) => <boolean>`**
83
84Returns a boolean based on whether or not the row at the given `index` has children.
85
86**`tree.search = ({ operation: (rows) => [<row>], idField = 'id', parentField = 'parent' }) => (rows) => [<searchedRow>]`**
87
88Searches against a tree structure using `operation` while matching against children too. If children are found, associated parents are returned as well. This has been designed to [searchtabular](https://www.npmjs.com/package/searchtabular) `multipleColumns` and `singleColumn`, but as long as the passed operation follows the interface, it should fit in.
89
90> This depends on [resolve.resolve](https://www.npmjs.com/package/table-resolver#resolveresolve)!
91
92**`tree.wrap = ({ operations: [rows => rows], idField = 'id' }) => (rows) => [<operatedRow>]`**
93
94If you want to perform an operation, such as sorting, against the root rows of a tree, use `tree.wrap`.
95
96**Example:**
97
98```javascript
99wrap({
100 operations: [
101 sorter({
102 columns,
103 sortingColumns,
104 sort: orderBy
105 })
106 ]
107})(rows);
108```
109
110### Packing
111
112**`tree.pack = ({ parentField = 'parent', childrenField = 'children', idField = 'id' }) => (rows) => [<packedRow>]`**
113
114Packs children inside root level nodes. This is useful with sorting and filtering.
115
116**`tree.unpack = ({ parentField = 'parent', childrenField = 'children', idField = 'id', parent }) => (rows) => [<unpackedRow>]`**
117
118Unpacks children from root level nodes. This is useful with sorting and filtering.
119
120### Drag and Drop
121
122**`tree.moveRows = ({ operation: (rows) => [<row>], retain = [], idField = 'id', parentField = 'parent' }) => (rows) => [<movedRow>]`**
123
124Allows moving tree rows while `retain`ing given fields at their original rows. You should pass an `operation` that performs actual moving here. [reactabular-dnd](https://www.npmjs.com/package/reactabular-dnd) `moveRows` is one option.
125
126### UI
127
128**`tree.toggleChildren = ({ getIndex, getRows, getShowingChildren, toggleShowingChildren, props, idField = 'id', parentField, toggleEvent = 'DoubleClick' }) => (value, extra) => <React element>`**
129
130Makes it possible to toggle node children through a user interface.
131Pass `"indent":false` inside `props` object if you want to disable automatic indentation.
132
133The default implementation of `getIndex(rowData)` depends on [resolve.resolve](https://www.npmjs.com/package/table-resolver#resolveresolve) as it looks for index of the row to toggle based on that. This can be customized though.
134
135### Helpers
136
137**`tree.fixOrder = ({ parentField = 'parent', idField = 'id' }) => (rows) => [<rows in correct order>]`**
138
139If children in your rows don't follow their parents you can use that helper method so they will be moved into right place.
140
141Basically it converts `[ parent, x, y, z, children ]` into `[ parent, children, x, y, z ]`.
142
143## Example
144
145```jsx
146/*
147import React from 'react';
148import cloneDeep from 'lodash/cloneDeep';
149import orderBy from 'lodash/orderBy';
150import { compose } from 'redux';
151import * as resolve from 'table-resolver';
152import VisibilityToggles from 'reactabular-visibility-toggles';
153import * as Table from 'reactabular-table';
154import * as tree from 'treetabular';
155import * as search from 'searchtabular';
156import * as sort from 'sortabular';
157
158import {
159 generateParents, generateRows
160} from './helpers';
161*/
162
163const schema = {
164 type: 'object',
165 properties: {
166 id: {
167 type: 'string'
168 },
169 name: {
170 type: 'string'
171 },
172 age: {
173 type: 'integer'
174 }
175 },
176 required: ['id', 'name', 'age']
177};
178
179class TreeTable extends React.Component {
180 constructor(props) {
181 super(props);
182
183 const columns = this.getColumns();
184 const rows = resolve.resolve({ columns })(
185 generateParents(generateRows(100, schema))
186 );
187
188 this.state = {
189 searchColumn: 'all',
190 query: {},
191 sortingColumns: null,
192 rows,
193 columns
194 };
195
196 this.onExpandAll = this.onExpandAll.bind(this);
197 this.onCollapseAll = this.onCollapseAll.bind(this);
198 this.onToggleColumn = this.onToggleColumn.bind(this);
199 }
200 getColumns() {
201 const sortable = sort.sort({
202 // Point the transform to your rows. React state can work for this purpose
203 // but you can use a state manager as well.
204 getSortingColumns: () => this.state.sortingColumns || {},
205
206 // The user requested sorting, adjust the sorting state accordingly.
207 // This is a good chance to pass the request through a sorter.
208 onSort: selectedColumn => {
209 const sortingColumns = sort.byColumns({
210 sortingColumns: this.state.sortingColumns,
211 selectedColumn
212 });
213
214 this.setState({ sortingColumns });
215 }
216 });
217
218 return [
219 {
220 property: 'name',
221 props: {
222 style: { width: 200 }
223 },
224 header: {
225 label: 'Name',
226 transforms: [sortable]
227 },
228 cell: {
229 formatters: [
230 tree.toggleChildren({
231 getRows: () => this.state.rows,
232 getShowingChildren: ({ rowData }) => rowData.showingChildren,
233 toggleShowingChildren: rowIndex => {
234 const rows = cloneDeep(this.state.rows);
235
236 rows[rowIndex].showingChildren = !rows[rowIndex].showingChildren;
237
238 this.setState({ rows });
239 },
240 // Inject custom class name per row here etc.
241 props: {}
242 })
243 ]
244 },
245 visible: true
246 },
247 {
248 property: 'age',
249 props: {
250 style: { width: 300 }
251 },
252 header: {
253 label: 'Age',
254 transforms: [sortable]
255 },
256 visible: true
257 }
258 ];
259 }
260 render() {
261 const {
262 searchColumn, columns, sortingColumns, query
263 } = this.state;
264 const visibleColumns = columns.filter(column => column.visible);
265 const rows = compose(
266 tree.filter({ fieldName: 'showingChildren' }),
267 tree.wrap({
268 operations: [
269 sort.sorter({
270 columns,
271 sortingColumns,
272 sort: orderBy
273 })
274 ]
275 }),
276 tree.search({
277 operation: search.multipleColumns({ columns, query })
278 })
279 )(this.state.rows);
280
281 return (
282 <div>
283 <VisibilityToggles
284 columns={columns}
285 onToggleColumn={this.onToggleColumn}
286 />
287
288 <button onClick={this.onExpandAll}>Expand all</button>
289 <button onClick={this.onCollapseAll}>Collapse all</button>
290
291 <div className="search-container">
292 <span>Search</span>
293 <search.Field
294 column={searchColumn}
295 query={query}
296 columns={visibleColumns}
297 rows={rows}
298 onColumnChange={searchColumn => this.setState({ searchColumn })}
299 onChange={query => this.setState({ query })}
300 />
301 </div>
302
303 <Table.Provider
304 className="pure-table pure-table-striped"
305 columns={visibleColumns}
306 >
307 <Table.Header />
308
309 <Table.Body rows={rows} rowKey="id" />
310 </Table.Provider>
311 </div>
312 );
313 }
314 onExpandAll() {
315 this.setState({
316 rows: tree.expandAll()(this.state.rows)
317 });
318 }
319 onCollapseAll() {
320 this.setState({
321 rows: tree.collapseAll()(this.state.rows)
322 });
323 }
324 onToggleColumn({ columnIndex }) {
325 const columns = cloneDeep(this.state.columns);
326
327 columns[columnIndex].visible = !columns[columnIndex].visible;
328
329 this.setState({ columns });
330 }
331}
332
333<TreeTable />
334```
335
336## License
337
338MIT. See LICENSE for details.