{"id":21018961,"url":"https://github.com/reactabular/treetabular","last_synced_at":"2025-05-15T06:31:57.422Z","repository":{"id":13591250,"uuid":"74823497","full_name":"reactabular/treetabular","owner":"reactabular","description":"Tree utilities (MIT)","archived":false,"fork":false,"pushed_at":"2022-12-07T04:15:14.000Z","size":825,"stargazers_count":11,"open_issues_count":15,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-04T20:04:00.337Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/reactabular.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-11-26T12:07:47.000Z","updated_at":"2023-02-25T13:41:17.000Z","dependencies_parsed_at":"2023-01-13T17:33:00.015Z","dependency_job_id":null,"html_url":"https://github.com/reactabular/treetabular","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactabular%2Ftreetabular","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactabular%2Ftreetabular/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactabular%2Ftreetabular/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reactabular%2Ftreetabular/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reactabular","download_url":"https://codeload.github.com/reactabular/treetabular/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254288336,"owners_count":22045883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-19T10:28:20.517Z","updated_at":"2025-05-15T06:31:55.850Z","avatar_url":"https://github.com/reactabular.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![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)\n\n# Treetabular - Tree utilities\n\n`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.\n\nTo achieve this, `treetabular` relies on a flat structure that contains the hierarchy:\n\n```javascript\nconst tree = [\n  {\n    _index: 0,\n    id: 123,\n    name: 'Demo'\n  },\n  {\n    _index: 1,\n    id: 456,\n    name: 'Another',\n    parent: 123\n  },\n  {\n    _index: 2,\n    id: 789,\n    name: 'Yet Another',\n    parent: 123\n  },\n  {\n    _index: 3,\n    id: 532,\n    name: 'Foobar'\n  }\n];\n```\n\nIf 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).\n\n\u003e You can find suggested default styling for the package at `style.css` in the package root.\n\n## API\n\n```javascript\nimport * as tree from 'treetabular';\n\n// Or you can cherry-pick\nimport { filter } from 'treetabular';\nimport { filter as filterTree } from 'treetabular';\n```\n\n### Transformations\n\n**`tree.collapseAll = ({ property = 'showingChildren' }) =\u003e (rows) =\u003e [\u003ccollapsedRow\u003e]`**\n\nCollapses rows by setting `showingChildren` of each row to `false`.\n\n**`tree.expandAll = ({ property = 'showingChildren' }) =\u003e (rows) =\u003e [\u003cexpandedRow\u003e]`**\n\nExpands rows by setting `showingChildren` of each row to `true`.\n\n**`tree.filter = ({ fieldName, idField = 'id', parentField = 'parent' }) =\u003e (rows) =\u003e [\u003cfilteredRow\u003e]`**\n\nFilters the given rows using `fieldName`. This is handy if you want only rows that are visible assuming visibility logic has been defined.\n\n### Queries\n\n**`tree.getLevel = ({ index, idField = 'parentId', parentField = 'parent' }) =\u003e (rows) =\u003e \u003clevel\u003e`**\n\nReturns the nesting level of the row at the given `index` within `rows`.\n\n**`tree.getChildren = ({ index, idField = 'id', parentField = 'parent' }) =\u003e (rows) =\u003e [\u003cchild\u003e]`**\n\nReturns children based on given `rows` and `index`. This includes children of children.\n\n**`tree.getImmediateChildren = ({ index, idField = 'id', parentField = 'parent' }) =\u003e (rows) =\u003e [\u003cchild\u003e]`**\n\nReturns immediate children based on given `rows` and `index`.\n\n**`tree.getParents = ({ index, idField = 'parentId', parentField = 'parent' }) =\u003e (rows) =\u003e [\u003cparent\u003e]`**\n\nReturns parents based on given `rows` and `index`.\n\n**`tree.hasChildren = ({ index, idField = 'id', parentField = 'parent '}) =\u003e (rows) =\u003e \u003cboolean\u003e`**\n\nReturns a boolean based on whether or not the row at the given `index` has children.\n\n**`tree.search = ({ operation: (rows) =\u003e [\u003crow\u003e], idField = 'id', parentField = 'parent' }) =\u003e (rows) =\u003e [\u003csearchedRow\u003e]`**\n\nSearches 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.\n\n\u003e This depends on [resolve.resolve](https://www.npmjs.com/package/table-resolver#resolveresolve)!\n\n**`tree.wrap = ({ operations: [rows =\u003e rows], idField = 'id' }) =\u003e (rows) =\u003e [\u003coperatedRow\u003e]`**\n\nIf you want to perform an operation, such as sorting, against the root rows of a tree, use `tree.wrap`.\n\n**Example:**\n\n```javascript\nwrap({\n  operations: [\n    sorter({\n      columns,\n      sortingColumns,\n      sort: orderBy\n    })\n  ]\n})(rows);\n```\n\n### Packing\n\n**`tree.pack = ({ parentField = 'parent', childrenField = 'children', idField = 'id' }) =\u003e (rows) =\u003e [\u003cpackedRow\u003e]`**\n\nPacks children inside root level nodes. This is useful with sorting and filtering.\n\n**`tree.unpack = ({ parentField = 'parent', childrenField = 'children', idField = 'id', parent }) =\u003e (rows) =\u003e [\u003cunpackedRow\u003e]`**\n\nUnpacks children from root level nodes. This is useful with sorting and filtering.\n\n### Drag and Drop\n\n**`tree.moveRows = ({ operation: (rows) =\u003e [\u003crow\u003e], retain = [], idField = 'id', parentField = 'parent' }) =\u003e (rows) =\u003e [\u003cmovedRow\u003e]`**\n\nAllows 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.\n\n### UI\n\n**`tree.toggleChildren = ({ getIndex, getRows, getShowingChildren, toggleShowingChildren, props, idField = 'id', parentField, toggleEvent = 'DoubleClick' }) =\u003e (value, extra) =\u003e \u003cReact element\u003e`**\n\nMakes it possible to toggle node children through a user interface.\nPass `\"indent\":false` inside `props` object if you want to disable automatic indentation.\n\nThe 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.\n\n### Helpers\n\n**`tree.fixOrder = ({ parentField = 'parent', idField = 'id' }) =\u003e (rows) =\u003e [\u003crows in correct order\u003e]`**\n\nIf children in your rows don't follow their parents you can use that helper method so they will be moved into right place.\n\nBasically it converts `[ parent, x, y, z, children ]` into `[ parent, children, x, y, z ]`.\n\n## Example\n\n```jsx\n/*\nimport React from 'react';\nimport cloneDeep from 'lodash/cloneDeep';\nimport orderBy from 'lodash/orderBy';\nimport { compose } from 'redux';\nimport * as resolve from 'table-resolver';\nimport VisibilityToggles from 'reactabular-visibility-toggles';\nimport * as Table from 'reactabular-table';\nimport * as tree from 'treetabular';\nimport * as search from 'searchtabular';\nimport * as sort from 'sortabular';\n\nimport {\n  generateParents, generateRows\n} from './helpers';\n*/\n\nconst schema = {\n  type: 'object',\n  properties: {\n    id: {\n      type: 'string'\n    },\n    name: {\n      type: 'string'\n    },\n    age: {\n      type: 'integer'\n    }\n  },\n  required: ['id', 'name', 'age']\n};\n\nclass TreeTable extends React.Component {\n  constructor(props) {\n    super(props);\n\n    const columns = this.getColumns();\n    const rows = resolve.resolve({ columns })(\n      generateParents(generateRows(100, schema))\n    );\n\n    this.state = {\n      searchColumn: 'all',\n      query: {},\n      sortingColumns: null,\n      rows,\n      columns\n    };\n\n    this.onExpandAll = this.onExpandAll.bind(this);\n    this.onCollapseAll = this.onCollapseAll.bind(this);\n    this.onToggleColumn = this.onToggleColumn.bind(this);\n  }\n  getColumns() {\n    const sortable = sort.sort({\n      // Point the transform to your rows. React state can work for this purpose\n      // but you can use a state manager as well.\n      getSortingColumns: () =\u003e this.state.sortingColumns || {},\n\n      // The user requested sorting, adjust the sorting state accordingly.\n      // This is a good chance to pass the request through a sorter.\n      onSort: selectedColumn =\u003e {\n        const sortingColumns = sort.byColumns({\n          sortingColumns: this.state.sortingColumns,\n          selectedColumn\n        });\n\n        this.setState({ sortingColumns });\n      }\n    });\n\n    return [\n      {\n        property: 'name',\n        props: {\n          style: { width: 200 }\n        },\n        header: {\n          label: 'Name',\n          transforms: [sortable]\n        },\n        cell: {\n          formatters: [\n            tree.toggleChildren({\n              getRows: () =\u003e this.state.rows,\n              getShowingChildren: ({ rowData }) =\u003e rowData.showingChildren,\n              toggleShowingChildren: rowIndex =\u003e {\n                const rows = cloneDeep(this.state.rows);\n\n                rows[rowIndex].showingChildren = !rows[rowIndex].showingChildren;\n\n                this.setState({ rows });\n              },\n              // Inject custom class name per row here etc.\n              props: {}\n            })\n          ]\n        },\n        visible: true\n      },\n      {\n        property: 'age',\n        props: {\n          style: { width: 300 }\n        },\n        header: {\n          label: 'Age',\n          transforms: [sortable]\n        },\n        visible: true\n      }\n    ];\n  }\n  render() {\n    const {\n      searchColumn, columns, sortingColumns, query\n    } = this.state;\n    const visibleColumns = columns.filter(column =\u003e column.visible);\n    const rows = compose(\n      tree.filter({ fieldName: 'showingChildren' }),\n      tree.wrap({\n        operations: [\n          sort.sorter({\n            columns,\n            sortingColumns,\n            sort: orderBy\n          })\n        ]\n      }),\n      tree.search({\n        operation: search.multipleColumns({ columns, query })\n      })\n    )(this.state.rows);\n\n    return (\n      \u003cdiv\u003e\n        \u003cVisibilityToggles\n          columns={columns}\n          onToggleColumn={this.onToggleColumn}\n        /\u003e\n\n        \u003cbutton onClick={this.onExpandAll}\u003eExpand all\u003c/button\u003e\n        \u003cbutton onClick={this.onCollapseAll}\u003eCollapse all\u003c/button\u003e\n\n        \u003cdiv className=\"search-container\"\u003e\n          \u003cspan\u003eSearch\u003c/span\u003e\n          \u003csearch.Field\n            column={searchColumn}\n            query={query}\n            columns={visibleColumns}\n            rows={rows}\n            onColumnChange={searchColumn =\u003e this.setState({ searchColumn })}\n            onChange={query =\u003e this.setState({ query })}\n          /\u003e\n        \u003c/div\u003e\n\n        \u003cTable.Provider\n          className=\"pure-table pure-table-striped\"\n          columns={visibleColumns}\n        \u003e\n          \u003cTable.Header /\u003e\n\n          \u003cTable.Body rows={rows} rowKey=\"id\" /\u003e\n        \u003c/Table.Provider\u003e\n      \u003c/div\u003e\n    );\n  }\n  onExpandAll() {\n    this.setState({\n      rows: tree.expandAll()(this.state.rows)\n    });\n  }\n  onCollapseAll() {\n    this.setState({\n      rows: tree.collapseAll()(this.state.rows)\n    });\n  }\n  onToggleColumn({ columnIndex }) {\n    const columns = cloneDeep(this.state.columns);\n\n    columns[columnIndex].visible = !columns[columnIndex].visible;\n\n    this.setState({ columns });\n  }\n}\n\n\u003cTreeTable /\u003e\n```\n\n## License\n\nMIT. See LICENSE for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactabular%2Ftreetabular","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freactabular%2Ftreetabular","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freactabular%2Ftreetabular/lists"}