Ecosyste.ms: Awesome

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

Awesome Lists | Featured Topics | Projects

https://github.com/valor-software/ng2-tree

Angular tree component
https://github.com/valor-software/ng2-tree

angular angular-tree async branch children ng2-tree tree treeview

Last synced: about 2 months ago
JSON representation

Angular tree component

Awesome Lists containing this project

README

        

# :herb: ng2-tree

[![npm](https://img.shields.io/npm/v/ng2-tree.svg?style=flat-square)](https://www.npmjs.com/package/ng2-tree)
[![Travis](https://img.shields.io/travis/valor-software/ng2-tree.svg?style=flat-square)](https://travis-ci.org/valor-software/ng2-tree)
[![Codecov](https://img.shields.io/codecov/c/github/valor-software/ng2-tree.svg?style=flat-square)](https://codecov.io/github/valor-software/ng2-tree)

* [:clapper: Usage](#clapper-usage)
* [:eyes: Demo](#eyes-demo)
* [:wrench: API](#wrench-api)
* [tree](#tree)
* [[tree]](#tree)
* [Load children asynchronously](#load-children-asynchronously)
* [Load children using ngrx (or any redux-like library)](#load-children-using-ngrx-or-any-redux-like-library)
* [Configure node via TreeModelSettings](#configure-node-via-treemodelsettings)
* [[settings]](#settings)
* [`Tree` class](#tree-class)
* [events (nodeMoved, nodeSelected, nodeRenamed, nodeRemoved, nodeCreated, nodeExpanded, nodeCollapsed)](#events-nodemoved-nodeselected-noderenamed-noderemoved-nodecreated-nodeexpanded-nodecollapsed)
* [NodeSelectedEvent](#nodeselectedevent)
* [NodeMovedEvent](#nodemovedevent)
* [NodeRemovedEvent](#noderemovedevent)
* [NodeCreatedEvent](#nodecreatedevent)
* [NodeRenamedEvent](#noderenamedevent)
* [NodeExpandedEvent](#nodeexpandedevent)
* [NodeCollapsedEvent](#nodecollapsedevent)
* [LoadNextLevelEvent](#loadnextlevelevent)
* [:gun: Controller](#gun-controller)
* [select - selects a node](#select---selects-a-node)
* [isSelected - checks whether a node is selected](#isselected---checks-whether-a-node-is-selected)
* [collapse - collapses a node](#collapse---collapses-a-node)
* [isCollapsed - check whether a node is collapsed](#iscollapsed---check-whether-a-node-is-collapsed)
* [expand - expands a node](#expand---expands-a-node)
* [isExpanded - checks whether a node is expanded](#isexpanded---checks-whether-a-node-is-expanded)
* [toTreeModel - converts a tree to a TreeModel instance](#totreemodel---converts-a-tree-to-a-treemodel-instance)
* [rename - renames a node (changes its value underneath)](#rename---renames-a-node-changes-its-value-underneath)
* [startRenaming - changes the node template so that text input appears and lets a user type a new name](#startrenaming---changes-the-node-template-so-that-text-input-appears-and-lets-a-user-type-a-new-name)
* [remove - removes a node from the tree](#remove---removes-a-node-from-the-tree)
* [addChild - creates a new child node](#addchild---creates-a-new-child-node)
* [changeNodeId - changes node's id](#changenodeid---changes-nodes-id)
* [reloadChildren - loads async children once more](#reloadchildren---loads-async-children-once-more)
* [setChildren - changes children of a node;](#setchildren---changes-children-of-a-node)
* [SystemJS](#systemjs)
* [Changes that should be taken into account in order to migrate from **ng2-tree V1** to **ng2-tree V2**](#changes-that-should-be-taken-into-account-in-order-to-migrate-from-__ng2-tree-v1__-to-__ng2-tree-v2__)
* [:bulb: Want to help?](#bulb-want-to-help)

## :clapper: Usage

Ok, let's start with an installation - all you need to do is:

`npm install --save ng2-tree`

Now when you have `ng2-tree` installed, you are in a few steps from having tree in your application:

1. Add the `TreeModule` to your application's module `imports` section:

```typescript
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BrowserModule } from '@angular/platform-browser';
import { TreeModule } from 'ng2-tree';

@NgModule({
declarations: [MyComponent],
imports: [BrowserModule, TreeModule],
bootstrap: [MyComponent]
})
export class MyModule {}
```

2. As soon as the previous step is done we need to give ng2-tree a model to render - this can be accomplished by populating its `[tree]` attribute with an object that conforms to the `TreeModel` interface (see [API](#wrench-api)):

```typescript
// 1 - import required classes and interfaces
import { TreeModel } from 'ng2-tree';

@Component({
selector: 'myComp',
// 2 - set [tree] attribute to tree object
template: ``
})
class MyComponent {
// 3 - make sure that tree object conforms to the TreeModel interface
public tree: TreeModel = {
value: 'Programming languages by programming paradigm',
children: [
{
value: 'Object-oriented programming',
children: [{ value: 'Java' }, { value: 'C++' }, { value: 'C#' }]
},
{
value: 'Prototype-based programming',
children: [{ value: 'JavaScript' }, { value: 'CoffeeScript' }, { value: 'Lua' }]
}
]
};
}
```

3. Apart from that, in order to have usable tree in the browser, you need to add **ng2-tree styles** which you can find in your `node_modules/ng2-tree/styles.css`

In Angular 2/4 cli projects, modify **.angular-cli.json** as below:

```typescript
"styles": [
"styles.css",
"../node_modules/ng2-tree/styles.css"
],
```

4. And finally, I suppose, you'd want to listen to events generated by ng2-tree (for a full list of supported events look at the [API](#wrench-api)). No problem, this is also easy to do - for example let's add a listener for `node was selected` kind of events:

```typescript
// 1 - import required classes and interfaces
import { TreeModel, NodeEvent } from 'ng2-tree';

@Component({
selector: 'myComp',
// 2 - listent for nodeSelected events and handle them
template: ``
})
class MyComponent {
public tree: TreeModel = { ... };

// 3 - print caught event to the console
public logEvent(e: NodeEvent): void {
console.log(e);
}
}
```

Voila! That's pretty much it - enjoy :blush:

## :eyes: Demo

Feel free to examine the [demo](https://valor-software.github.io/ng2-tree/index.html) and its [sources](src/demo) to find out how things are wired.
Also, there is [another demo built with Angular CLI](https://github.com/rychkog/ng2-tree-demo).

## :wrench: API

Here is the fully stuffed _tree_ tag that you can use in your templates:

```html


```

Let's go through every element of this structure one by one.

### tree

`tree` is the selector for `TreeComponent` which is bundled into `TreeModule`:

### [tree]

`tree` has a `[tree]` attribute which needs to be populated with an object implementing `TreeModel` interface. You can import this interface like below:

```typescript
import { TreeModel } from 'ng2-tree';
```

Here is the definition of the `TreeModel` interface:

```typescript
interface TreeModel {
value: string | RenamableNode;
id: string | number;
children?: Array;
loadChildren?: ChildrenLoadingFunction;
settings?: TreeModelSettings;
}
```

As you can see - an object that conforms to this interface has a recursive nature, an example can be seen below:

```typescript
{
value: 'Programming languages by programming paradigm',
children: [
{
value: 'Object-oriented programming',
children: [
{value: 'Java'},
{value: 'C++'},
{value: 'C#'}
]
},
{
value: 'Prototype-based programming',
children: [
{value: 'JavaScript'},
{value: 'CoffeeScript'},
{value: 'Lua'}
]
}
]
}
```

Property `value` can be of type `string` or `RenamableNode`.
`RenamableNode` gives you an additional control over the way node is renamed and rendered (by rendered I mean its text representation). Here is the definition of the `RenamableNode` interface:

```typescript
interface RenamableNode {
// This method will be invoked in order to apply new value to this kind of node
setName(name: string): void;

// This method will be invoked in order to get a text for rendering as a node value
toString(): string;
}
```

Here is an example of such a node in the `TreeModel` object:

```typescript
{
value: 'Programming languages by programming paradigm',
children: [
{
value: 'Object-oriented programming',
children: [
{
// I am a RenamableNode. Yeah, that's me :)
value: {
name: 'Java',
setName(name: string): void {
this.name = name;
},
toString(): string {
return this.name;
}
}
},
{value: 'C++'},
{value: 'C#'}
]
},
{
value: 'Prototype-based programming',
loadChildren: (callback) => {
setTimeout(() => {
callback([
{value: 'JavaScript'},
{value: 'CoffeeScript'},
{value: 'TypeScript'}
]);
}, 5000);
}
}
]
};
```

#### Load children asynchronously

Another worth noting thing is `loadChildren`. This function on `TreeModel` allows you to load its **children asynchronously**.

```typescript
{
value: 'Prototype-based programming',
loadChildren: (callback) => {
setTimeout(() => {
callback([
{value: 'JavaScript'},
{value: 'CoffeeScript'},
{value: 'TypeScript'}
]);
}, 5000);
}
}
```

Node that defines this function is collapsed by default. At the moment of clicking 'Expand' arrow, it starts loading its children by calling given function.
If `loadChildren` function is given to the node - `children` property is ignored. For more details - have a look at the [Demo](#eyes-demo).

#### Load children using ngrx (or any redux-like library)

You can also load children by changing the tree state using ngrx.
The tree can emit an appropriate event notifying you to dispatch a new action in order to load the branch's children.

To enable this feature you should set the `TreeModel.emitLoadNextLevel` property to true:

```typscript
const model: TreeModel = {
emitLoadNextLevel : true
}
```

Now on the first time the node is expanded a **LoadNextLevelEvent** will be fired (via the **loadNextLevel** EventEmitter in the tree) containing the node that requested a next level (its children) loading.

In your code make sure you change the tree state and add the children to the model.

In addition the regular **NodeExpanded** event will be fired.

**NOTICE**: if both `emitLoadNextLevel` and `loadChildren` are provided, the tree will ignore the `emitLoadNextLevel` and the `LoadNextLevelEvent` won't be fired.

#### Configure node via TreeModelSettings

Apart from that `TreeModel` interface has an optional field called `settings` of type `TreeModelSettings`.

Here is an example of its usage:

```typescript
{
value: 'Prototype-based programming',
settings: {
'static': true,
'rightMenu': true,
'leftMenu': true,
'cssClasses': {
'expanded': 'fa fa-caret-down fa-lg',
'collapsed': 'fa fa-caret-right fa-lg',
'leaf': 'fa fa-lg',
'empty': 'fa fa-caret-right disabled'
},
'templates': {
'node': '',
'leaf': '',
'leftMenu': ''
},
'menuItems': [
{ action: NodeMenuItemAction.Custom, name: 'Foo', cssClass: 'fa fa-arrow-right' },
{ action: NodeMenuItemAction.Custom, name: 'Bar', cssClass: 'fa fa-arrow-right' },
{ action: NodeMenuItemAction.Custom, name: 'Baz', cssClass: 'fa fa-arrow-right'}
]
}
},
children: [
{value: 'JavaScript'},
{value: 'CoffeeScript'},
{value: 'Lua'}
]
}
```

* `static` - Boolean - This option makes it impossible to drag a tree or modify it in a some way, though you still can select nodes in the static tree and appropriate events will be generated.
* `isCollapsedOnInit` - Boolean - This option makes a tree to be collapsed on first load (this option cascades to its children).
* `rightMenu` - Boolean - This option allows you to activate (true, by default) or deactivate (false) right menu when clicking with right button of a mouse.
* `leftMenu` - Boolean - This option allows you to activate (true) or deactivate (false, by default) left menu.
* `cssClasses` - Object:
* `expanded` - String - It specifies a css class (or classes) for an item which represents expanded state of a node. The item is clickable and it transitions the node to the collapsed state
* `collapsed` - String - It specifies a css class (or classes) for an item which represents collapsed state of a node. The item is clickable and it transitions the node to the expanded state
* `leaf` - String - It specifies a css class (or classes) for an item which represents a node without an option to expand or collapse - in other words: a leaf node.
* `empty` - String - Node is considered empty when it has no children. Once this condition is satisfied - appropriate css class will be applied to the node.
* `templates` - Object:
* `node` - String - It specifies a html template which will be included to the left of the node's value.
* `leaf` - String - It specifies a html template which will be included to the left of the leaf's value.
* `leftMenu` - String - It specifies a html template to the right of the node's value. This template becomes clickable and shows a menu on node's click.
* `menuItems` - here you can specify your custom menu items. You should feed an array of NodeMenuItem instances to this setting. Once done - setup a subscription to `MenuItemSelectedEvent`s by listening to `(menuItemSelected)="onMenuItemSelected($event)"` on the tree.

All options that are defined on a `parent` are automatically applied to children. If you want you can override them by `settings` of the child node.

### [settings]

Object that should be passed to `[settings]` must be of type [`Ng2TreeSettings`](src/tree.types.ts). This attribute is **optional**. Right now only one setting is available in there - `rootIsVisible`. This setting allows you to make a root node of the tree _invisible_:

```typescript
const treeSettings: Ng2TreeSettings = {
rootIsVisible: false
};
```

By default `rootIsVisible` equals to `true`

### `Tree` class

Also in the next section, you'll be reading about events generated by the `ng2-tree`. And here [Tree](src/tree.ts) class comes in handy for us, because its instances propagated with event objects. Under the hood, `ng2-tree` wraps a `TreeModel` provided by the user in `Tree`. And `Tree` in turn has lots of useful methods and properties (like `parent`, `hasChild()`, `isRoot()` etc.)

### events (nodeMoved, nodeSelected, nodeRenamed, nodeRemoved, nodeCreated, nodeExpanded, nodeCollapsed)

`NodeEvent` is the root of the tree events' hierarchy. It defines property `node` that contains a receiver of the event action (`node` is an instance of the `Tree` class).

`NodeDestructiveEvent` is the parent for all events that cause changes to the structure of the tree or to the node's value.

#### NodeSelectedEvent

You can subscribe to the `NodeSelectedEvent` by attaching listener to the `(nodeSelected)` attribute

```html


```

`NodeSelectedEvent` has just one property `node` which contains a `Tree` object representing selected node.

```typescript
{node: {...}}
```

#### NodeMovedEvent

You can subscribe to `NodeMovedEvent` by attaching listener to `(nodeMoved)` attribute

```html


```

`NodeMovedEvent` has two properties `node` and `previousParent` both of which contain `Tree` objects:

* `node` contains a moved node;
* `previousParent` contains a previous parent of the moved node;

```typescript
{node: {...}, previousParent: {...}}
```

#### NodeRemovedEvent

You can subscribe to `NodeRemovedEvent` by attaching listener to `(nodeRemoved)` attribute

```html


```

`NodeRemovedEvent` has a `node` property, which contains removed node (of type `Tree`).

```typescript
{node: {...}}
```

#### NodeCreatedEvent

You can subscribe to `NodeCreatedEvent` by attaching listener to `(nodeCreated)` attribute

```html


```

`NodeCreatedEvent` has a `node` property of type `Tree`, which contains a created node and a `controller` property, which will give you access to node's controller.

```typescript
{node: {...}}
```

#### NodeRenamedEvent

You can subscribe to `NodeRenamedEvent` by attaching listener to `(nodeRenamed)` attribute

```html


```

`NodeRenamedEvent` has three properties:

* `node` contains a node that was renamed ( an instance of `Tree`).
* `oldValue` contains a value, that node used to have (it might be `string` or `RenamableNode`)
* `newValue` contains a new value of the node (it might be `string` or `RenamableNode`)

```typescript
{
node: {...},
oldValue: {...},
newValue: {...}
}
```

#### NodeExpandedEvent

You can subscribe to `NodeExpandedEvent` by attaching listener to `(nodeExpanded)` attribute, this event wont fire on initial expansion

```html


```

`NodeExpandedEvent` has a `node` property of type `Tree`, which contains an expanded node.

```typescript
{node: {...}}
```

#### NodeCollapsedEvent

You can subscribe to `NodeCollapsedEvent` by attaching listener to `(nodeCollapsed)` attribute

```html


```

`NodeCollapsedEvent` has a `node` property of type `Tree`, which contains a collapsed node.

```typescript
{node: {...}}
```

#### LoadNextLevelEvent

You can subscribe to `LoadNextLevelEvent` by attaching a listener to `(loadNextLevel)` attribute.
Relevant for loading children via ngrx (or any redux-inspired library).

```html


```

`LoadNextLevelEvent` has a `node` property of the type `Tree`, which contains a node for which next level (its children) should be loaded.

```typescript
{node: {...}}
```

## :gun: Controller

First of all you should know how to get a controller of a particular node. You can get a controller of a node only if you set an id property of a node.

> TIP: Ids for nodes created via the context menu or using a TreeController instance get populated automatically unless nodes had ids before there were added to the tree

For example, your tree structure should look like:

```typescript
public tree: TreeModel = {
value: 'Programming languages by programming paradigm',
id: 1,
children: [
{
value: 'Object-oriented programming',
id: 2,
children: [
{value: 'Java', id: 3},
{value: 'C++', id: 4},
{value: 'C#', id 5},
]
},
{
value: 'Prototype-based programming',
id: 6,
children: [
{value: 'JavaScript', id: 7},
{value: 'CoffeeScript', id: 8},
{value: 'Lua', id: 9},
]
}
]
};
```

Ids must be unique within a one tree, otherwise, some controllers will be overwritten and you won't be able to acquire them.
In order to get a node's controller you need to create an Angular local variable out of tree component via hash binding in the template:

```typescript
@Component({
template: ''
})
class TheComponent implements AfterViewInit {
tree: TreeModel = {
value: 'Programming languages by programming paradigm',
id: 1,
children: [
{
value: 'Object-oriented programming',
id: 2,
children: [
{value: 'Java', id: 3},
{value: 'C++', id: 4},
{value: 'C#', id 5},
]
},
{
value: 'Prototype-based programming',
id: 6,
children: [
{value: 'JavaScript', id: 7},
{value: 'CoffeeScript', id: 8},
{value: 'Lua', id: 9},
]
}
]
};

@ViewChild('treeComponent') treeComponent;

ngAfterViewInit(): void {
// ... make use of this.treeComponent ...
}
}
```

then by executing `this.treeComponent.getControllerByNodeId(PUT_HERE_YOUR_NODE_ID)` you'll get an instance of a TreeController (another couple steps and the world is yours =) )

Below are more detailed explanations of the TreeController and its usage. Let's go method by method:

```typescript
const oopNodeController = this.treeComponent.getControllerByNodeId(2);
```

#### select - selects a node

```typescript
oopNodeController.select();
```

This method selects the node and unselects all the other nodes, also it fires a select event.

#### isSelected - checks whether a node is selected

```typescript
oopNodeController.isSelected();
```

This method returns true if the node is selected and false if it isn't.

#### collapse - collapses a node

```typescript
oopNodeController.collapse();
```

This method collapses a node if the node is collapsible (for example we cannot collapse a leaf). If the node gets collapsed successfully - a collapse event gets fired.

#### isCollapsed - check whether a node is collapsed

```typescript
oopNodeController.isCollapsed();
```

This method returns true if the node is collapsed and false otherwise.

#### expand - expands a node

```typescript
oopNodeController.expand();
```

This method expands the node in case it can be expanded. On successful expanding the expand event is fired.

#### expandToParent - expands a node and its parents up to the root

```typescript
oopNodeController.expandToParent();
```

This method expands the node even if it is a leaf. Expand event is fired for every expanded parent up to the root.

**Important:** For this to work - `keepNodesInDOM: true` should be set on the appropriate tree.

#### isExpanded - checks whether a node is expanded

```typescript
oopNodeController.isExpanded();
```

#### toTreeModel - converts a tree to a TreeModel instance

Actually controller makes and returns a clone of tree's underlying model

```typescript
oopNodeController.toTreeModel();
```

This method returns true if the node is expanded and false otherwise.

#### rename - renames a node (changes its value underneath)

```typescript
oopNodeController.rename('new value');
```

This method accepts a string and sets it as a node's new value, this action also fires rename event.

#### startRenaming - changes the node template so that text input appears and lets a user type a new name

```typescript
oopNodeController.startRenaming();
```

After the user entered the new name a rename event will be fired.

#### remove - removes a node from the tree

```typescript
oopNodeController.remove();
```

This method removes the node and its children and fires remove event.

#### addChild - creates a new child node

```typescript
let newNode: TreeModel = {
value: 'Go',
children: []
};
oopNodeController.addChild(newNode);
```

This method accepts a TreeModel and adds it as a child of the parent or as a sibling (depends on which controller this was called - branch controller or a leaf controller).

### changeNodeId - changes node's id

```typescript
oopNodeController.changeNodeId(10);
```

This method can change a node's id. When the user creates a node from node's menu you will access the new node after it's created and this method will provide a way to change the node's id.

### reloadChildren - loads async children once more

```typescript
oopNodeController.reloadChildren();
```

### setChildren - changes children of a node;

```typescript
let newChildren: Array = [
{ value: 'new children 1' },
{ value: 'new children 2' },
{ value: 'new children 3' }
];
oopNodeController.setChildren(newChildren);
```

This method replaces all existing children of the node with new ones.

## SystemJS

If you are using SystemJS, then you need

```javascript
System.config({
// ...
map: {
// ...
'ng2-tree': 'node_modules/ng2-tree/bundles/ng2-tree.umd.min.js',
// ...
},
// ...
}
```

## Changes that should be taken into account in order to migrate from **ng2-tree V1** to **ng2-tree V2**

* Events were reworked:
* In V1 all events that were inherited from NodeDestructiveEvent used to have property `parent`. It's not the case anymore. If you need a parent you should get it from `node` in event object like `node.parent`;
* All events used to have `node` property of type `TreeModel`. Now `node` is of type [Tree](#tree-class) (as well as `node.parent`);
* `NodeMovedEvent` now has property `previousParent`, which contains tree in which moved node used to be.
* CSS styles in **ng2-tree V2** are distributed as separate file which you can find in `node_modules/ng2-tree/styles.css`. That allows you to override ng2-tree styles more easily.

## :bulb: Want to help?

I am very appreciate for your ideas, proposals and found bugs which you can put in [github issues](https://github.com/valor-software/ng2-tree/issues). Thanks in advance!

**P.S.** If you find it hard going through the documentation, please, let me know which parts of it was difficult to grasp and I will improve them.