Coder Social home page Coder Social logo

ngcx-tree's Introduction

A reusable tree component for Angular based on the CDK Tree and the CDK Drag n Drop features.

Includes only peer dependencies to angular cdk, no other library included. So the lib size is pretty small.

Demo:

https://cluetec.github.io/ngcx-tree-demo

Feature Overview

  • Highly custumizable tree component
    • Configure your Angular Template for node rendering
    • Or configure your own component for node rendering
  • Selection state
    • configure which nodes are selectable
  • Drag and Drop
    • configure which nodes may be moved
    • configure where a node can be dropped
  • Events (always with detailed information about parents, next or prev. nodes):
    • On move
    • On click
    • On select/unselect
    • custom event may be emitted by custom component

Missing features? Let me know :)

Table of Content

Getting Started

  1. Install the library:
npm install @cluetec/ngcx-tree
  1. Import the component. Since it is standalone, either add it directly to another standlone component or import it into your existing NgModule:
import { NgcxTreeComponent } from '@cluetec/ngcx-tree';

@Component({
  standalone: true,
  imports: [NgcxTreeComponent],
})
import { NgcxTreeComponent } from '@cluetec/ngcx-tree';

@NgModule({
  declarations: [AppComponent],
  imports: [NgcxTreeComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}
<ngcx-tree [nodes]="nodes"></ngcx-tree>

Prerequisites

You need at least angular 16 to use the tree.



Inputs

Property Type required Description
nodes NgcxTreeNode[] no list of nodes to show in the tree
config NgcxTreeConfig no used to render the node when no custom template or component is set

Outputs

Property event content type Description
nodeMoved NgcxTreeNodeMovedEvent fired when a node is moved
customEvent any may be fired by your own custom component
clickEvent NgcxTreeNodeWrapper fired when node is clicked
selectEvent NgcxTreeNodeWrapper fired when node is selected or un-selected. Clicking a selected node un-selects the node.

Model

NgcxTreeConfig

The component includes a model called NgcxTreeConfig with some basic optional settings.

  • allowDrag method that decides if a node can be dragged: (node: NgcxTreeNodeWrapper<T>) => boolean - all nodes are draggable by default

  • allowDrop method that decides if node can be dropped into another node (node: NgcxTreeNodeWrapper<T>, intoNode?: NgcxTreeNodeWrapper<T>) => boolean - every node may be draggable everywhere by default.

  • preventDropReason method that decides if node can be dropped into another node (node: NgcxTreeNodeWrapper<T>, intoNode?: NgcxTreeNodeWrapper<T>) => string - every node may be draggable everywhere by default. If a non empty string is returned, then the node cannot be dropped and the string will be displayed. One of the methods allowDrop or preventDropReason is sufficient to prevent the node to be dropped.

  • allowSelection method that decides if node can be selected (node: NgcxTreeNodeWrapper<T>) => boolean - nodes are not selectable by default

  • treeNodeContentTemplate Angular TemplateRef that will be used to render a node

    let-nodeWrapper="nodeWrapper" may be used to access the node wrapper to render the node
  • treeNodeContentComponent Angular Component that will be used to render a node. (use treeNodeContentComponent or treeNodeContentTemplate, but not both). see NgcxCustomComponent



NgcxTreeNode

If no data is passed to the component, it will simply display some mock data. Data is provided to the tree in the following format:

Property Type required Description
id string yes necessary unique id of the node
title string no used to render the node when no custom template or component is set
faIcon string no font awesome icon used to render the node when no custom template or component is set. You must include fontawesome on your own if you want
children NgcxTreeNode[] no children of the node

NgcxTreeNodeWrapper

Generic T is the same as the elements of the input nodes.

Property Type Description
id string same as NgcxTreeNode.id
data T data of the input nodes nodes
depth number depth in the tree starting with 0
index number index of the node in it's parent
isSelectable boolean if the node is selectable. Depending on the config.allowSelection method. (default: false)
isFirstChild boolean is first node from the same parent
isLastChild boolean is last node from the same parent
children NgcxTreeNodeWrapper[] list of children wrappers around the original nodes
parent NgcxTreeNodeWrapper | undefined parent node
next NgcxTreeNodeWrapper | undefined node after this node in same parent, if one exists.
previous NgcxTreeNodeWrapper | undefined node before this node in same parent, if one exists.

NgcxTreeNodeMovedEvent

Property Type Description
node NgcxTreeNodeWrapper the moved node
parent NgcxTreeNodeWrapper | undefined moved into this parent node
afterNode NgcxTreeNodeWrapper | undefined moved to position after this node
beforeNode NgcxTreeNodeWrapper | undefined moved to position before this node

NgcxCustomComponent

Your component can implement this interface and can be set as Type<NgcxCustomComponent<T>> in the config.treeNodeContentComponent input.

Input

nodeWrapper the input to render the node. Type: NgcxTreeNodeWrapper

Output

customEvent EventEmitter<any> can be used to trigger the output 'customEvent'

TreeControl - Api

Access api like this

import { ViewChild, Component } from '@angular/core';
import { NgcxTreeComponent, NgcxTreeNode } from '@cluetec/ngcx-tree';

@Component({
  selector: 'app-expand-tree-sample',
  template: ```
  <button (click)="expandAll()"></button>
  <ngcx-tree #tree [nodes]="nodes"></ngcx-tree>
  ```,
  standalone: true,
  imports: [NgcxTreeComponent],
})
export class ExpandTreeSampleComponent {
  nodes = [];
  @ViewChild('tree', { static: false })
  ngcxTree: NgcxTreeComponent<NgcxTreeNode>;

  expandAll(): void {
    this.ngcxTree.treeControl.expandAll();
  }
}

treeControl

The treeControl extends the treeControl from Angular CDK (NestedTreeControl<NgcxTreeNodeWrapper<T>, string>) and can mainly be used to expand and collapse nodes.

Additional Helper methods

selectNodeById

Can be called to select a node by id. the selectEvent event is fired afterwards.

findNodeById

Can be used to get the NgcxTreeNodeWrapper<T> for an id. returns undefined if no node is available for the id.

Styling

Include Styles

styles.scss and styles.css contains all the parts described below in one file:

@import 'node_modules/@cluetec/ngcx-tree/styles/styles';

CSS Variables

Css Variables may be defined to customize the Look and feel like this:

  --ngcx-tree-color-no-drop: #ff0000;
Variable name Description
ngcx-tree-color-no-drop Color which is shown at the drop box dashed border or line to indicate that drop is not possible here. You may set to transparent if you don't want the border in this case

Common styling

you should set the width of cdk-drop-list to auto, otherwise, the node content may be on wrong place:

.ngcx-tree .cdk-drop-list {
  width: auto;
}

Or Include this:

@import 'node_modules/@cluetec/ngcx-tree/styles/ngcx-common';

Dotted tree lines

Import or copy the scss to show tree lines:

@import 'node_modules/@cluetec/ngcx-tree/styles/ngcx-doted-tree-line';

Selection highlighting

Import or copy the scss to show some selection styling:

@import 'node_modules/@cluetec/ngcx-tree/styles/ngcx-selection';

Icon color

Import or copy the scss to set the color of the node icon:

@import 'node_modules/@cluetec/ngcx-tree/styles/ngcx-icon-color';

Prevent drop reason tooltip

Import or copy the scss to style the tooltip, shown when config.preventDropReason is used:

@import 'node_modules/@cluetec/ngcx-tree/styles/ngcx-tooltip';

Font Awesome

Font Awesome is not included here, but to show icons for the nodes you may include font-awesome on your own and may use the node.faIcon property to set the icon.

Include like this: projects/ngcx-tree/stories/styles/styles.scss

Selection

Selected node can be styled like this:

.tree-node-content-container.selected .tree-node-content {
  background-color: #555;
  padding-left: 5px;
}

Hover effect on selectable node:

.ngcx-tree:not(.dragging)
  .tree-node-content-container.is-selectable:hover
  .tree-node-content {
  background-color: #fbfbfb;
}

Remove Selection css on dragging element:

.cdk-drag-preview .tree-node-content-container.selected .tree-node-content {
  background-color: inherit;
}

Simple Sample

import { Component } from '@angular/core';
import { NgcxTreeComponent } from '@cluetec/ngcx-tree';

@Component({
  selector: 'app-simple-tree-sample',
  template: '<ngcx-tree [nodes]="nodes"></ngcx-tree>',
  standalone: true,
  imports: [NgcxTreeComponent],
})
export class SimpleTreeSampleComponent {
  nodes = [
    {
      id: 'fru',
      title: 'Fruit',
      children: [
        {
          id: 'app',
          title: 'Apple',
        },
        {
          id: 'ban',
          title: 'Banana',
        },
      ],
    },
    {
      id: 'fish',
      title: 'Fish',
    },
  ];
}



Contributions

Contributions and improvement suggestions are always welcome!

Samples

You can run Storybook and see the samples there.

  1. npm run build
  2. npm run storybook

ngcx-tree's People

Contributors

mn-42 avatar dependabot[bot] avatar johnny-mcfly avatar lennardboehnke avatar philkoepfer avatar

Stargazers

 avatar Etienne avatar  avatar  avatar  avatar

Watchers

Florian Rusch avatar Iskandar Abudiab avatar René Albers avatar N4tht4N avatar  avatar

ngcx-tree's Issues

DropZoneId undefined

First of all, thank you for this library which works very well. However, I have noticed an issue where sometimes the drop function does not work. This remains very rare but still occurs, and I was able to reproduce it using the demo link. Upon closer inspection, I noticed that the dropZoneId variable within the handleDragRelease method was undefined. The value of <any>event.event.target was equal to <div class="drop-insert-line"></div>.

ngcxtree.mp4

To correct this, a solution would be to check the parent if dropZoneId is undefined:

const target = <HTMLDivElement>event.event.target;
const dropZoneId = target.id || target.parentElement?.id;
if (!dropZoneId) {
  // no valid drop zone
  return;
}

What do you think? If the solution seems relevant, I would be happy to open a PR.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.