ng2-tree-pms
Version:
angular2 component for visualizing data that can be naturally represented as a tree
416 lines (328 loc) • 13.2 kB
Markdown
# :herb: ng2-tree-pms
ng2-tree is a simple [Angular 2](https://github.com/angular/angular) component for visualizing data that can be naturally represented as a tree.
<!-- TOC -->
- [:clapper: Usage](#clapper-usage)
- [:eyes: Demo](#eyes-demo)
- [:wrench: API](#wrench-api)
- [tree](#tree)
- [[tree]](#tree)
- [Load children asynchronously](#load-children-asynchronously)
- [Configure node via TreeModelSettings](#configure-node-via-treemodelsettings)
- [[settings]](#settings)
- [`Tree` class](#tree-class)
- [events (nodeMoved, nodeSelected, nodeRenamed, nodeRemoved, nodeCreated)](#events-nodemoved-nodeselected-noderenamed-noderemoved-nodecreated)
- [NodeSelectedEvent](#nodeselectedevent)
- [NodeMovedEvent](#nodemovedevent)
- [NodeRemovedEvent](#noderemovedevent)
- [NodeCreatedEvent](#nodecreatedevent)
- [NodeRenamedEvent](#noderenamedevent)
- [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)
<!-- /TOC -->
## :clapper: Usage
Ok, let's start with an installation - all you need to do is:
`npm install --save ng2-tree-pms`
Now when you have `ng2-tree-pms` 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-pms';
@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-pms';
@Component({
selector: 'myComp',
// 2 - set [tree] attribute to tree object
template: `<tree [tree]="tree"></tree>`
})
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`
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: `<tree [tree]="tree" (nodeSelected)="logEvent($event)"></tree>`
})
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) and its [sources](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
<tree
[tree]="tree"
[settings]="settings"
(nodeRemoved)="handleRemoved($event)"
(nodeRenamed)="handleRenamed($event)"
(nodeSelected)="handleSelected($event)"
(nodeMoved)="handleMoved($event)"
(nodeCreated)="handleCreated($event)">
</tree>
```
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-pms';
```
Here is the definition of the `TreeModel` interface:
```typescript
interface TreeModel {
value: string | RenamableNode;
children?: Array<TreeModel>;
loadChildren?: ChildrenLoadingFunction;
settings?: TreeModelSettings;
typeModel: string;
}
```
As you can see - object that conforms to this interface has a recursive nature, 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: <RenamableNode>{
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).
#### 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
},
children: [
{value: 'JavaScript'},
{value: 'CoffeeScript'},
{value: 'Lua'},
]
}
```
Right now only one option is supported - `static`. 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.
`static` option that's defined on a `parent` is automatically applied to its children. If you don't want to make `static` all the children, then you can override `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)
Here is the diagram that shows tree events' hierarchy

`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
<tree
[tree]="tree"
(nodeSelected)="handleSelected($event)">
</tree>
```
`NodeSelectedEvent` has just one property `node` which contains a `Tree` object representing selected node.
```typescript
{node: <Tree>{...}}
```
#### NodeMovedEvent
You can subscribe to `NodeMovedEvent` by attaching listener to `(nodeMoved)` attribute
```html
<tree
[tree]="tree"
(nodeMoved)="handleMoved($event)">
</tree>
```
`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: <Tree>{...}, previousParent: <Tree>{...}}
```
#### NodeRemovedEvent
You can subscribe to `NodeRemovedEvent` by attaching listener to `(nodeRemoved)` attribute
```html
<tree
[tree]="tree"
(nodeRemoved)="handleRemoved($event)">
</tree>
```
`NodeRemovedEvent` has a `node` property, which contains removed node (of type `Tree`).
```typescript
{node: <Tree>{...}}
```
#### NodeCreatedEvent
You can subscribe to `NodeCreatedEvent` by attaching listener to `(nodeCreated)` attribute
```html
<tree
[tree]="tree"
(nodeCreated)="handleCreated($event)">
</tree>
```
`NodeCreatedEvent` has a `node` property of type `Tree`, which contains a created node.
```typescript
{node: <Tree>{...}}
```
#### NodeRenamedEvent
You can subscribe to `NodeRenamedEvent` by attaching listener to `(nodeRenamed)` attribute
```html
<tree
[tree]="tree"
(nodeRenamed)="handleRenamed($event)">
</tree>
```
`NodeRenamedEvent` has three properties:
- `node` contains node that was renamed (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: <Tree>{...},
oldValue: <string|RenamableNode>{...},
newValue: <string|RenamableNode>{...}
}
```
## 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 easely.
## :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 documentation, please, let me know which parts of it was difficult to grasp and I will improve them.