Ecosyste.ms: Awesome

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

Awesome Lists | Featured Topics | Projects

https://github.com/07akioni/treemate

Manipulating tree data structure for user interface
https://github.com/07akioni/treemate

Last synced: about 2 months ago
JSON representation

Manipulating tree data structure for user interface

Awesome Lists containing this project

README

        

# treemate · [![Coverage Status](https://coveralls.io/repos/github/07akioni/treemate/badge.svg)](https://coveralls.io/github/07akioni/treemate)

https://treemate.vercel.app

All in one solution for tree structure in component developing.

It helps you manipulate tree data structure for user interface. (Can be used in Tree, Select, Dropdown, Table, Menu components and ...)

1. check & uncheck nodes in the tree
2. expand & collapse nodes in the tree
3. move along tree nodes
4. get flattened nodes
5. query nodes
6. support group node (group a set of nodes in the same tree level)
7. support ignored node (ignored by move)
8. meta info of nodes
9. get the original node data ref
10. async check
11. ...

## Installation

```bash
npm i -D treemate
```

## Basic Concepts

Before you started, I strongly recommend you to read the section to get the basic concepts of treemate.

### Node, Group Node and Ignored Node

In treemate, a tree is composed of node (optional group node and optional ignored).

A node contains a key, and maybe a children prop which includes its child node.

#### Node

```
Node {
key,
children?
}
```

#### Group Node

If you don't need group node, you can pass the section.

A group node contains a key, a type prop with value `group` and a children of its child node (can be a group node any more).

```
GroupNode {
key,
type: 'group',
children
}
```

The group node itself will be ignored when moving along the nodes, and the children of the group node is view as the same level of the group node.

For example, in the following tree, the group child 1 will be viewed as node 1's next node. node 2 will be viewed as group child 2's next node.

```
- node 1
- group node 1
- group child 1
- group child 2
- node 2
```

#### Ignored Node

Some time's you may want to put some render only nodes in the tree. For example:

```
- node 1
- divider (render only)
- node 2
```

In data aspect, the divider node is meaning less. You can make it a ignored node. The ignored node will be ignored when moving along the nodes. (node 2 will be view the next node as node 1.) Also, `getNode` method won't return the ignored node.

An ignored node should also contains a key (for modern frontend framework to do efficient diff)

```
IgnoredNode {
key,
type: 'ignored'
}
```

## Usage

### Create a Treemate

`createTreeMate` method accepts a array of node as data. It returns a treemate instance.

In javascript:

```js
import { createTreeMate } from 'treemate'

const data = [
// non-leaf node
{
key: 1,
children: [
{
key: 2
}
]
},
// leaf node
{
key: 3
},
// ignored node
{
key: 4,
type: 'ignored'
},
// group node
{
key: 5,
type: 'group',
children: [
{
key: 6
}
]
}
]

const treeMate = createTreeMate(data)
```

In typescript, the data looks almost same. However `createTreeMate` accepts 3 optional generic parameter to specify the types of node, group node and ignored node.

```ts
interface BaseNode {
key: string | number
children?: Array
}

interface GroupNode {
key: string | number
type: 'group'
children: Array
}

interface IgnoredNode {
key: string | number
type: 'ignored'
}

// 1. specify all node types
const treeMate = createTreeMate(data)

// 2. equals to createTreeMate()
// which mean no ignored node is in the data.
const treeMate = createTreeMate(data)

// 3. equals to createTreeMate()
// which mean no ignored node and group node is in the data.
const treeMate = createTreeMate(data)

// 4. without generic parameter
// it will use a builtin node type as basic node type.
// RawNode {
// key: string | number
// children?: RawNode[]
// disabled?: boolean
// isLeaf? boolean
// }
// ignored node and group node should be in the data.
const treeMate = createTreeMate(data)
```

### Custom `createTreeMate` Options

If you want another way to determine a way to specify if a node's key or its disabled, group, ignored status. You can pass an option when create treemate.

```ts
const treeMate = createTreeMate(data, {
getKey: (node) => Key,
getDisabled: (node) => boolean,
getIsGroup: (node) => boolean,
getIgnored: (node) => boolean
})
```

### Get a Node from Tree

Now suppose we have a treemate instance.

```ts
const tmNode = treeMate.getNode(key) // if not exist return null

// Caveat: getNode won't return group node & ignored node!
// If you do need to get them, you can use the treeNodeMap, eg:
treeMate.treeNodeMap.get(key)
```

### Props of a TreeNode

```js
TreeNode {
key,
rawNode, // hold the ref to the original data node, can be very useful
level, // from 0
index, // its index in its parent node (or root array)
siblings,
isFirstChild,
isLastChild,
parent, // its parent TreeNode (not data node)
isShallowLoaded, // use when on partial data is loaded
isLeaf,
isGroup,
ignored, // boolean
disabled, // disabled
children?, // its child TreeNodes (not data node)
getPrev, // method
getNext, // method
getParent, // method
getChild // method
}
```

### Do Check and Uncheck in the Tree

#### `TreeMate.getCheckedKeys(checkedKeys, options?)`

Get checked status of the tree.

Node has `disabled = true` will be block cascade check's propagation.

Param `checkedKeys` has two forms:

```ts
Key[] // 1. currently checked keys

// 2. merged checked status
interface InputMergedKeys {
checkedKeys?: Key[] | null
indeterminateKeys?: Key[] | null // half checked
}

// it can also be
null | undefined
// viewed as an empty array
```

Param `options` looks like

```ts
interface CheckOptions {
cascade?: boolean // cascade check status, default is true
leafOnly?: boolean // whether only allow leaf node being checked, default is false
checkStrategy?: string // set show strategy when checked 'all' | 'parent' | 'child'
}
```

Return value looks like

```ts
interface MergedKeys {
checkedKeys: Key[]
indeterminateKeys: Key[] // half checked
}
```

##### Usage

```ts
const { checkedKeys, indeterminateKeys } = treeMate.getCheckedKeys([1])
const { checkedKeys, indeterminateKeys } = treeMate.getCheckedKeys([1], {
cascade: false
})
const { checkedKeys, indeterminateKeys } = treeMate.getCheckedKeys({
checkedKeys: [1],
indeterminateKeys: [2]
})
// ...
```

#### `TreeMate.check(keysToCheck, checkedKeys, options?)`

Get checked status of the tree after some nodes are checked.

`keysToCheck` could be `Key | Key[] | null | undefined`.

For `checkedKeys`, `options` and return value, see `getCheckedKeys(checkedKeys, options?)`.

#### `TreeMate.uncheck(keysToUncheck, checkedKeys, options?)`

Get checked status of the tree after some nodes are unchecked.

`keysToCheck` could be `Key | Key[] | null | undefined`.

For `checkedKeys`, `options` and return value, see `getCheckedKeys(checkedKeys, options?)`.

### Do Move in the Tree

#### `TreeMate.getPrev(key, options?)`

Get the first previous not `disabled` sibling `TreeMateNode` of the `key`'s corresponding node. In the traverse process, the `group | ignored` node itself will be dismissed. If node doesn't exist, return `null`.

`options` look like `{ loop?: boolean }`. By default, `loop` is `false`, it won't loop when touches the last node.

#### `TreeMate.getNext(key, options?)`

Get the first next not `disabled` sibling `TreeMateNode` of the `key`'s corresponding node. In the traverse process, the `group | ignored` node itself will be dismissed. If node doesn't exist, return `null`.

`options` look like `{ loop?: boolean }`. By default, `loop` is `false`, it won't loop when touches the last node.

#### `TreeMate.getParent(key)`

Get the parent node of the `key`'s corresponding node. In the traverse process, the `group` node itself will be dismissed. If node doesn't exist, return `null`.

#### `TreeMate.getChild(key)`

Get the first not `disabled` child node of the `key`'s corresponding node. In the traverse process, the `group | ignored` node itself will be dismissed. If node doesn't exist, return `null`.

#### `TreeNode.getPrev(options?)`

Get the first previous not `disabled` sibling `TreeMateNode`. In the traverse process, the `group` node itself will be dismissed. If node doesn't exist, return `null`.

`options` look like `{ loop?: boolean }`. By default, `loop` is `false`, it won't loop when touches the last node.

#### `TreeNode.getNext(options?)`

Get the first next not `disabled` sibling `TreeMateNode`. In the traverse process, the `group | ignored` node itself will be dismissed. If node doesn't exist, return `null`.

`options` look like `{ loop?: boolean }`. By default, `loop` is `false`, it won't loop when touches the last node.

#### `TreeNode.getParent()`

Get the parent node of `TreeMateNode`. In the traverse process, the `group` node itself will be dismissed. If node doesn't exist, return `null`.

#### `TreeNode.getChild()`

Get the first not `disabled` child `TreeMateNode`. In the traverse process, the `group | ignored` node itself will be dismissed. If node doesn't exist, return `null`.

### Do Expand & Collapsed on the Tree

Expand status is will influence the flattened nodes of the tree. The flattened nodes is crucial for virtual list.

#### `TreeMate.getFlattenedNodes(expandedKeys?)`

Returns the flattened tree nodes with corresponding `expandedKeys`. If `expandedKeys` is not provided, treemate will treat it as all expanded.

#### `createIndexGetter(flattenedNodes)`

Create an index getter from the `flattenedNodes`.

```ts
import { createIndexGetter } from 'treemate'

const getIndex = createIndexGetter(flattenedNodes)

getIndex(flattenedNodes[0].key) === 0
```

### Get Path of a Node

#### `TreeMate.getPath(key, options?)`

Get the path from root to the node corresponding to the `key`. The return value looks like

```ts
interface MergedPath {
keyPath: Key[]
treeNodePath: TreeMateNode[]
treeNode: TreeMateNode | null
}
```

The `keyPath` is the `key` of the nodes in path. The `treeNodePath` is the node path. `treeNode` is the `TreeMateNode` corresponding to the `key`.

`options` looks like `{ includeSelf?: boolean, includeGroup?: boolean }`, by default `includeSelf` is true and `includeGroup` is false.

### Get First Available Node of the Tree

Can be used to get the default pending status of a select menu.

#### `TreeMate.getFirstAvailableNode()`

Get the first not `disabled` `TreeMateNode` of the tree. In the traverse process, the `group | ignored` node itself will be dismissed. If node doesn't exist, returns `null`.

### Other Props in TreeMate Instance

#### `TreeMate.treeNodes`

Corresponding `TreeMateNode` Array of original data. The tree structure is identical to the original data.

#### `TreeMate.treeNodeMap`

A map of `key` to tree node. Contains all nodes, including `group | ignored` node.