From 4a68b647b44747ac9eba37c79f78954ec7dcd33b Mon Sep 17 00:00:00 2001 From: TMaster Date: Fri, 17 Nov 2017 14:17:05 +0200 Subject: [PATCH 1/5] Support checking / unchecking nodes --- src/demo/app/app.component.ts | 264 ++++++++++++++++++--------------- src/tree-controller.ts | 9 +- src/tree-internal.component.ts | 66 ++++++++- src/tree.component.ts | 21 ++- src/tree.events.ts | 12 ++ src/tree.service.ts | 14 +- src/tree.ts | 1 + src/tree.types.ts | 4 +- 8 files changed, 257 insertions(+), 134 deletions(-) diff --git a/src/demo/app/app.component.ts b/src/demo/app/app.component.ts index d246647b..9d3a088a 100644 --- a/src/demo/app/app.component.ts +++ b/src/demo/app/app.component.ts @@ -51,7 +51,8 @@ declare const alertify: any; (nodeMoved)="onNodeMoved($event)" (nodeCreated)="onNodeFFSCreated($event)" (nodeExpanded)="onNodeExpanded($event)" - (nodeCollapsed)="onNodeCollapsed($event)"> + (nodeCollapsed)="onNodeCollapsed($event)" + [settings]="settings"> @@ -76,6 +77,9 @@ declare const alertify: any; + +
@@ -172,7 +176,8 @@ declare const alertify: any; }) export class AppComponent implements OnInit { public settings: Ng2TreeSettings = { - rootIsVisible: false + rootIsVisible: false, + enableCheckboxes: true }; public fonts: TreeModel = { @@ -185,18 +190,18 @@ export class AppComponent implements OnInit { 'static': true }, children: [ - {value: 'Antiqua with HTML tags.', id: 2}, - {value: 'DejaVu Serif', id: 3}, - {value: 'Garamond', id: 4}, - {value: 'Georgia', id: 5}, - {value: 'Times New Roman', id: 6}, + { value: 'Antiqua with HTML tags.', id: 2 }, + { value: 'DejaVu Serif', id: 3 }, + { value: 'Garamond', id: 4 }, + { value: 'Georgia', id: 5 }, + { value: 'Times New Roman', id: 6 }, { value: 'Slab serif', id: 7, children: [ - {value: 'Candida', id: 8}, - {value: 'Swift', id: 9}, - {value: 'Guardian Egyptian', id: 10} + { value: 'Candida', id: 8 }, + { value: 'Swift', id: 9 }, + { value: 'Guardian Egyptian', id: 10 } ] } ] @@ -205,29 +210,29 @@ export class AppComponent implements OnInit { value: 'Sans-serif', id: 11, children: [ - {value: 'Arial', id: 12}, - {value: 'Century Gothic', id: 13}, - {value: 'DejaVu Sans', id: 14}, - {value: 'Futura', id: 15}, - {value: 'Geneva', id: 16}, - {value: 'Liberation Sans', id: 17} + { value: 'Arial', id: 12 }, + { value: 'Century Gothic', id: 13 }, + { value: 'DejaVu Sans', id: 14 }, + { value: 'Futura', id: 15 }, + { value: 'Geneva', id: 16 }, + { value: 'Liberation Sans', id: 17 } ] }, { value: 'Monospace - With ASYNC CHILDREN', id: 18, // children property is ignored if "loadChildren" is present - children: [{value: 'I am the font that will be ignored'}], + children: [{ value: 'I am the font that will be ignored' }], loadChildren: (callback) => { setTimeout(() => { callback([ - {value: 'Input Mono', id: 19}, - {value: 'Roboto Mono', id: 20}, - {value: 'Liberation Mono', id: 21}, - {value: 'Hack', id: 22}, - {value: 'Consolas', id: 23}, - {value: 'Menlo', id: 24}, - {value: 'Source Code Pro', id: 25} + { value: 'Input Mono', id: 19 }, + { value: 'Roboto Mono', id: 20 }, + { value: 'Liberation Mono', id: 21 }, + { value: 'Hack', id: 22 }, + { value: 'Consolas', id: 23 }, + { value: 'Menlo', id: 24 }, + { value: 'Source Code Pro', id: 25 } ]); }, 5000); } @@ -243,6 +248,7 @@ export class AppComponent implements OnInit { value: '/', id: 1, settings: { + cssClasses: { expanded: 'fa fa-caret-down', collapsed: 'fa fa-caret-right', @@ -259,16 +265,16 @@ export class AppComponent implements OnInit { value: 'bin', id: 2, children: [ - {value: 'bash', id: 3}, - {value: 'umount', id: 4}, - {value: 'cp', id: 5}, - {value: 'less', id: 6}, - {value: 'rmdir', id: 7}, - {value: 'touch', id: 8}, - {value: 'chgrp', id: 9}, - {value: 'chmod', id: 10}, - {value: 'chown', id: 11}, - {value: 'nano', id: 12} + { value: 'bash', id: 3 }, + { value: 'umount', id: 4 }, + { value: 'cp', id: 5 }, + { value: 'less', id: 6 }, + { value: 'rmdir', id: 7 }, + { value: 'touch', id: 8 }, + { value: 'chgrp', id: 9 }, + { value: 'chmod', id: 10 }, + { value: 'chown', id: 11 }, + { value: 'nano', id: 12 } ] }, { @@ -279,13 +285,13 @@ export class AppComponent implements OnInit { value: 'grub', id: 14, children: [ - {value: 'fonts', id: 15}, - {value: 'gfxblacklist.txt', id: 16}, - {value: 'grub.cfg', id: 17}, - {value: 'grubenv', id: 18}, - {value: 'i386-pc', id: 19}, - {value: 'locale', id: 20}, - {value: 'unicode.pf2', id: 21} + { value: 'fonts', id: 15 }, + { value: 'gfxblacklist.txt', id: 16 }, + { value: 'grub.cfg', id: 17 }, + { value: 'grubenv', id: 18 }, + { value: 'i386-pc', id: 19 }, + { value: 'locale', id: 20 }, + { value: 'unicode.pf2', id: 21 } ] }, { @@ -293,15 +299,15 @@ export class AppComponent implements OnInit { id: 22, children: [] }, - {value: 'abi-4.4.0-57-generic', id: 23}, - {value: 'config-4.4.0-57-generic', id: 24}, - {value: 'initrd.img-4.4.0-47-generic', id: 25}, - {value: 'initrd.img-4.4.0-57-generic', id: 26}, - {value: 'memtest86+.bin', id: 27}, - {value: 'System.map-4.4.0-57-generic', id: 28}, - {value: 'memtest86+.elf', id: 29}, - {value: 'vmlinuz-4.4.0-57-generic', id: 30}, - {value: 'memtest86+_multiboot.bin', id: 31} + { value: 'abi-4.4.0-57-generic', id: 23 }, + { value: 'config-4.4.0-57-generic', id: 24 }, + { value: 'initrd.img-4.4.0-47-generic', id: 25 }, + { value: 'initrd.img-4.4.0-57-generic', id: 26 }, + { value: 'memtest86+.bin', id: 27 }, + { value: 'System.map-4.4.0-57-generic', id: 28 }, + { value: 'memtest86+.elf', id: 29 }, + { value: 'vmlinuz-4.4.0-57-generic', id: 30 }, + { value: 'memtest86+_multiboot.bin', id: 31 } ] }, { @@ -335,8 +341,8 @@ export class AppComponent implements OnInit { } ] }, - {value: 'cdrom', id: 34, children: []}, - {value: 'dev', id: 35, children: []}, + { value: 'cdrom', id: 34, children: [] }, + { value: 'dev', id: 35, children: [] }, { value: 'etc', id: 36, @@ -344,10 +350,10 @@ export class AppComponent implements OnInit { console.log('callback function called to load etc`s children'); setTimeout(() => { callback([ - {value: 'apache2', id: 82, children: []}, - {value: 'nginx', id: 83, children: []}, - {value: 'dhcp', id: 84, children: []}, - {value: 'dpkg', id: 85, children: []} + { value: 'apache2', id: 82, children: [] }, + { value: 'nginx', id: 83, children: [] }, + { value: 'dhcp', id: 84, children: [] }, + { value: 'dpkg', id: 85, children: [] } ]); }); } @@ -372,38 +378,38 @@ export class AppComponent implements OnInit { value: 'bills', id: 41, children: [ - {value: '2016-07-01-mobile.pdf', id: 42}, - {value: '2016-07-01-electricity.pdf', id: 43}, - {value: '2016-07-01-water.pdf', id: 44}, - {value: '2016-07-01-internet.pdf', id: 45}, - {value: '2016-08-01-mobile.pdf', id: 46}, - {value: '2016-10-01-internet.pdf', id: 47} + { value: '2016-07-01-mobile.pdf', id: 42 }, + { value: '2016-07-01-electricity.pdf', id: 43 }, + { value: '2016-07-01-water.pdf', id: 44 }, + { value: '2016-07-01-internet.pdf', id: 45 }, + { value: '2016-08-01-mobile.pdf', id: 46 }, + { value: '2016-10-01-internet.pdf', id: 47 } ] }, - {value: 'photos', id: 48, children: []} + { value: 'photos', id: 48, children: [] } ] } ] }, - {value: 'Downloads', id: 49, children: []}, - {value: 'Desktop', id: 50, children: []}, - {value: 'Pictures', id: 51, children: []}, + { value: 'Downloads', id: 49, children: [] }, + { value: 'Desktop', id: 50, children: [] }, + { value: 'Pictures', id: 51, children: [] }, { value: 'Music', id: 52, - children: [{value: 'won\'t be displayed'}], + children: [{ value: 'won\'t be displayed' }], loadChildren: (callback) => { setTimeout(() => { callback([ - {value: '2Cellos', id: 78, children: []}, - {value: 'Michael Jackson', id: 79, children: []}, - {value: 'AC/DC', id: 80, children: []}, - {value: 'Adel', id: 81, children: []} + { value: '2Cellos', id: 78, children: [] }, + { value: 'Michael Jackson', id: 79, children: [] }, + { value: 'AC/DC', id: 80, children: [] }, + { value: 'Adel', id: 81, children: [] } ]); }, 5000); } }, - {value: 'Public', id: 53, children: []} + { value: 'Public', id: 53, children: [] } ] }, { @@ -413,7 +419,7 @@ export class AppComponent implements OnInit { leftMenu: true }, children: [ - {value: 'Documents', id: 55, children: []}, + { value: 'Documents', id: 55, children: [] }, { value: 'Downloads - custom left menu template', id: 56, @@ -423,33 +429,33 @@ export class AppComponent implements OnInit { } }, children: [ - {value: 'Actobat3', id: 57}, - {value: 'Complib', id: 58}, - {value: 'Eudora', id: 59}, - {value: 'java', id: 60}, - {value: 'drivers', id: 61}, - {value: 'kathy', id: 62} + { value: 'Actobat3', id: 57 }, + { value: 'Complib', id: 58 }, + { value: 'Eudora', id: 59 }, + { value: 'java', id: 60 }, + { value: 'drivers', id: 61 }, + { value: 'kathy', id: 62 } ] }, - {value: 'Desktop', id: 63, children: []}, - {value: 'Pictures', id: 64, children: []}, - {value: 'Music', id: 65, children: []}, - {value: 'Public', id: 66, children: []} + { value: 'Desktop', id: 63, children: [] }, + { value: 'Pictures', id: 64, children: [] }, + { value: 'Music', id: 65, children: [] }, + { value: 'Public', id: 66, children: [] } ] } ] }, - {value: 'lib', id: 67, children: []}, - {value: 'media', id: 68, children: []}, - {value: 'opt', id: 69, children: []}, - {value: 'proc', id: 70, children: []}, - {value: 'root', id: 71, children: []}, - {value: 'run', id: 72, children: []}, - {value: 'sbin', id: 73, children: []}, - {value: 'srv', id: 74, children: []}, - {value: 'sys', id: 75, children: []}, - {value: 'usr', id: 76, children: []}, - {value: 'var', id: 77, children: []} + { value: 'lib', id: 67, children: [] }, + { value: 'media', id: 68, children: [] }, + { value: 'opt', id: 69, children: [] }, + { value: 'proc', id: 70, children: [] }, + { value: 'root', id: 71, children: [] }, + { value: 'run', id: 72, children: [] }, + { value: 'sbin', id: 73, children: [] }, + { value: 'srv', id: 74, children: [] }, + { value: 'sys', id: 75, children: [] }, + { value: 'usr', id: 76, children: [] }, + { value: 'var', id: 77, children: [] } ] }; private lastFFSNodeId = 86; @@ -462,31 +468,31 @@ export class AppComponent implements OnInit { { value: 'Web Application Icons', children: [ - {value: 'calendar', icon: 'fa-calendar' }, - {value: 'download', icon: 'fa-download' }, - {value: 'group', icon: 'fa-group' }, - {value: 'print', icon: 'fa-print' } + { value: 'calendar', icon: 'fa-calendar' }, + { value: 'download', icon: 'fa-download' }, + { value: 'group', icon: 'fa-group' }, + { value: 'print', icon: 'fa-print' } ] }, { value: 'Hand Icons', children: [ - {value: 'pointer', icon: 'fa-hand-pointer-o' }, - {value: 'grab', icon: 'fa-hand-rock-o' }, - {value: 'thumbs up', icon: 'fa-thumbs-o-up ' }, - {value: 'thumbs down', icon: 'fa-thumbs-o-down' } + { value: 'pointer', icon: 'fa-hand-pointer-o' }, + { value: 'grab', icon: 'fa-hand-rock-o' }, + { value: 'thumbs up', icon: 'fa-thumbs-o-up ' }, + { value: 'thumbs down', icon: 'fa-thumbs-o-down' } ] }, { value: 'File Type Icons', children: [ - {value: 'file', icon: 'fa-file-o' }, - {value: 'audio', icon: 'fa-file-audio-o' }, - {value: 'movie', icon: 'fa-file-movie-o ' }, - {value: 'archive', icon: 'fa-file-zip-o' } + { value: 'file', icon: 'fa-file-o' }, + { value: 'audio', icon: 'fa-file-audio-o' }, + { value: 'movie', icon: 'fa-file-movie-o ' }, + { value: 'archive', icon: 'fa-file-zip-o' } ] }, - ] + ] }; private static logEvent(e: NodeEvent, message: string): void { @@ -502,8 +508,8 @@ export class AppComponent implements OnInit { { value: 'Aspect-oriented programming', children: [ - {value: 'AspectJ'}, - {value: 'AspectC++'} + { value: 'AspectJ' }, + { value: 'AspectC++' } ] }, { @@ -520,16 +526,16 @@ export class AppComponent implements OnInit { } } as RenamableNode }, - {value: 'C++'}, - {value: 'C#'} + { value: 'C++' }, + { value: 'C#' } ] }, { value: 'Prototype-based programming', children: [ - {value: 'JavaScript'}, - {value: 'CoffeeScript'}, - {value: 'TypeScript'} + { value: 'JavaScript' }, + { value: 'CoffeeScript' }, + { value: 'TypeScript' } ] } ] @@ -594,11 +600,11 @@ export class AppComponent implements OnInit { const treeController = this.treeFFS.getControllerByNodeId(id); if (treeController && typeof treeController.setChildren === 'function') { treeController.setChildren([ - {value: 'apache2', id: 82, children: []}, - {value: 'nginx', id: 83, children: []}, - {value: 'dhcp', id: 84, children: []}, - {value: 'dpkg', id: 85, children: []}, - {value: 'gdb', id: 86, children: []} + { value: 'apache2', id: 82, children: [] }, + { value: 'nginx', id: 83, children: [] }, + { value: 'dhcp', id: 84, children: [] }, + { value: 'dpkg', id: 85, children: [] }, + { value: 'gdb', id: 86, children: [] } ]); } else { console.log('There isn`t a controller for a node with id - ' + id); @@ -614,4 +620,24 @@ export class AppComponent implements OnInit { console.log(`Controller is absent for a node with id: ${id}`); } } + + public checkFolder(id: number): void { + const treeController = this.treeFFS.getControllerByNodeId(id); + if (treeController) { + treeController.check(); + } else { + console.log(`Controller is absent for a node with id: ${id}`); + } + + } + + public uncheckFolder(id: number): void { + const treeController = this.treeFFS.getControllerByNodeId(id); + if (treeController) { + treeController.uncheck(); + } else { + console.log(`Controller is absent for a node with id: ${id}`); + } + + } } diff --git a/src/tree-controller.ts b/src/tree-controller.ts index 90e2423c..747cba79 100644 --- a/src/tree-controller.ts +++ b/src/tree-controller.ts @@ -88,6 +88,13 @@ export class TreeController { public startRenaming(): void { this.tree.markAsBeingRenamed(); + } - } + public check() : void { + this.component.onNodeChecked() +} + +public uncheck() : void { + this.component.onNodeUnchecked() +} } diff --git a/src/tree-internal.component.ts b/src/tree-internal.component.ts index 98d9ee8b..7e106add 100644 --- a/src/tree-internal.component.ts +++ b/src/tree-internal.component.ts @@ -1,10 +1,11 @@ -import { Component, ElementRef, TemplateRef, Inject, Input, OnDestroy, OnInit, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, ElementRef, TemplateRef, Inject, Input, OnDestroy, OnInit, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; import * as TreeTypes from './tree.types'; import { Tree } from './tree'; import { TreeController } from './tree-controller'; import { NodeMenuService } from './menu/node-menu.service'; import { NodeMenuItemAction, NodeMenuItemSelectedEvent } from './menu/menu.events'; import { NodeEditableEvent, NodeEditableEventAction } from './editable/editable.events'; +import { NodeRemovedEvent, NodeCheckedEvent } from './tree.events' import { TreeService } from './tree.service'; import * as EventUtils from './utils/event.utils'; import { NodeDraggableEvent } from './draggable/draggable.events'; @@ -24,6 +25,11 @@ import { get } from './utils/fn.utils'; [tree]="tree">
+ +
+ +
+
- @@ -49,7 +55,7 @@ import { get } from './utils/fn.utils'; - + @@ -68,13 +74,18 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { public isSelected = false; public isRightMenuVisible = false; public isLeftMenuVisible = false; + public isChecked = false; public controller: TreeController; + + @ViewChild('checkbox') + _checkboxElement: ElementRef; + private subscriptions: Subscription[] = []; - public constructor(@Inject(NodeMenuService) private nodeMenuService: NodeMenuService, - @Inject(TreeService) public treeService: TreeService, - @Inject(ElementRef) public element: ElementRef) { + public constructor( @Inject(NodeMenuService) private nodeMenuService: NodeMenuService, + @Inject(TreeService) public treeService: TreeService, + @Inject(ElementRef) public element: ElementRef) { } public ngOnInit(): void { @@ -83,7 +94,9 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { this.treeService.setController(this.tree.node.id, this.controller); } - this.settings = this.settings || { rootIsVisible: true }; + this.settings = this.settings || { rootIsVisible: true, enableCheckboxes: false }; + + this.isChecked = this.tree.isChecked; this.subscriptions.push(this.nodeMenuService.hideMenuStream(this.element) .subscribe(() => { @@ -235,4 +248,43 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { public isRootHidden(): boolean { return this.tree.isRoot() && !this.settings.rootIsVisible; } + + public NodeCheckSatusChanged() { + if (!this.isChecked) { + this.onNodeChecked(); + } + else { + this.onNodeUnchecked(); + } + + } + + public onNodeChecked(): void { + this._checkboxElement.nativeElement.indeterminate = false; + this.treeService.fireNodeChecked(this.tree); + this.executeOnChildController(controller => controller.check()); + this.isChecked = true; + this.tree.isChecked = true; + } + + public onNodeUnchecked(): void { + this._checkboxElement.nativeElement.indeterminate = false; + + this.treeService.fireNodeUnchecked(this.tree); + + this.executeOnChildController(controller => controller.uncheck()); + this.isChecked = false; + this.tree.isChecked = false; + } + + private executeOnChildController(executor: (controller: TreeController) => void) { + if (this.tree.children) { + this.tree.children.forEach((child: Tree) => { + let controller = this.treeService.getController(child.id); + if (controller != null) { + executor(controller); + } + }); + } + } } diff --git a/src/tree.component.ts b/src/tree.component.ts index 58409faa..b888913b 100644 --- a/src/tree.component.ts +++ b/src/tree.component.ts @@ -4,7 +4,7 @@ import { } from '@angular/core'; import { TreeService } from './tree.service'; import * as TreeTypes from './tree.types'; -import { NodeEvent } from './tree.events'; +import { NodeEvent, NodeCheckedEvent, NodeUncheckedEvent } from './tree.events'; import { Tree } from './tree'; import { TreeController } from './tree-controller'; import { Subscription } from 'rxjs/Subscription'; @@ -15,7 +15,7 @@ import { Subscription } from 'rxjs/Subscription'; providers: [TreeService] }) export class TreeComponent implements OnInit, OnChanges, OnDestroy { - private static EMPTY_TREE: Tree = new Tree({value: ''}); + private static EMPTY_TREE: Tree = new Tree({ value: '' }); /* tslint:disable:no-input-rename */ @Input('tree') @@ -46,8 +46,14 @@ export class TreeComponent implements OnInit, OnChanges, OnDestroy { @Output() public nodeCollapsed: EventEmitter = new EventEmitter(); -@Output() -public loadNextLevel: EventEmitter = new EventEmitter(); + @Output() + public loadNextLevel: EventEmitter = new EventEmitter(); + + @Output() + public nodeChecked: EventEmitter = new EventEmitter(); + + @Output() + public nodeUnchecked: EventEmitter = new EventEmitter(); public tree: Tree; @ViewChild('rootComponent') public rootComponent; @@ -56,7 +62,7 @@ public loadNextLevel: EventEmitter = new EventEmitter(); private subscriptions: Subscription[] = []; - public constructor(@Inject(TreeService) private treeService: TreeService) { + public constructor( @Inject(TreeService) private treeService: TreeService) { } public ngOnChanges(changes: SimpleChanges): void { @@ -99,6 +105,11 @@ public loadNextLevel: EventEmitter = new EventEmitter(); this.subscriptions.push(this.treeService.loadNextLevel$.subscribe((e: NodeEvent) => { this.loadNextLevel.emit(e); })); + + this.subscriptions.push(this.treeService.nodeChecked$.subscribe((e: NodeCheckedEvent) => this.nodeChecked.emit(e))); + + this.subscriptions.push(this.treeService.nodeUnchecked$.subscribe((e: NodeUncheckedEvent) => this.nodeChecked.emit(e))); + } public getController(): TreeController { diff --git a/src/tree.events.ts b/src/tree.events.ts index ac546b68..4c060211 100644 --- a/src/tree.events.ts +++ b/src/tree.events.ts @@ -60,3 +60,15 @@ export class LoadNextLevelEvent extends NodeEvent { super(node); } } + +export class NodeCheckedEvent extends NodeEvent { + public constructor(node: Tree) { + super(node); + } +} + +export class NodeUncheckedEvent extends NodeEvent { + public constructor(node: Tree) { + super(node); + } +} diff --git a/src/tree.service.ts b/src/tree.service.ts index 5b3f30cb..dfcbcfff 100644 --- a/src/tree.service.ts +++ b/src/tree.service.ts @@ -6,7 +6,9 @@ import { NodeRemovedEvent, NodeRenamedEvent, NodeSelectedEvent, - LoadNextLevelEvent + LoadNextLevelEvent, + NodeCheckedEvent, + NodeUncheckedEvent } from './tree.events'; import { RenamableNode } from './tree.types'; import { Tree } from './tree'; @@ -27,6 +29,8 @@ export class TreeService { public nodeExpanded$: Subject = new Subject(); public nodeCollapsed$: Subject = new Subject(); public loadNextLevel$: Subject = new Subject(); + public nodeChecked$ : Subject = new Subject(); + public nodeUnchecked$ : Subject = new Subject(); private controllers: Map = new Map(); @@ -81,6 +85,14 @@ export class TreeService { this.loadNextLevel$.next(new LoadNextLevelEvent(tree)); } + public fireNodeChecked(tree: Tree) : void { + this.nodeChecked$.next(new NodeCheckedEvent(tree)); + } + + public fireNodeUnchecked(tree: Tree) : void { + this.nodeUnchecked$.next(new NodeUncheckedEvent(tree)); + } + public draggedStream(tree: Tree, element: ElementRef): Observable { return this.nodeDraggableService.draggableNodeEvents$ .filter((e: NodeDraggableEvent) => e.target === element) diff --git a/src/tree.ts b/src/tree.ts index 98d9875e..5a68bb4e 100644 --- a/src/tree.ts +++ b/src/tree.ts @@ -40,6 +40,7 @@ export class Tree { public node: TreeModel; public parent: Tree; + public isChecked: boolean; // STATIC METHODS ---------------------------------------------------------------------------------------------------- diff --git a/src/tree.types.ts b/src/tree.types.ts index 562d163b..7862a1c6 100644 --- a/src/tree.types.ts +++ b/src/tree.types.ts @@ -26,6 +26,7 @@ export interface TreeModel { _status?: TreeStatus; _foldingType?: FoldingType; [additionalData: string]: any; + checked?: boolean; } export interface CssClasses { @@ -85,7 +86,7 @@ export class TreeModelSettings { public static?: boolean; public static merge(sourceA: TreeModel, sourceB: TreeModel): TreeModelSettings { - return defaultsDeep({}, get(sourceA, 'settings'), get(sourceB, 'settings'), {static: false, leftMenu: false, rightMenu: true}); + return defaultsDeep({}, get(sourceA, 'settings'), get(sourceB, 'settings'), { static: false, leftMenu: false, rightMenu: true }); } } @@ -96,6 +97,7 @@ export interface Ng2TreeSettings { * @type boolean */ rootIsVisible?: boolean; + enableCheckboxes?: boolean; } export enum TreeStatus { From e255869473e355c2778b41e8b2be0e4fd80dbcd6 Mon Sep 17 00:00:00 2001 From: TMaster Date: Fri, 24 Nov 2017 12:51:18 +0200 Subject: [PATCH 2/5] Support readonly checkboxes --- src/demo/app/app.component.ts | 13 ++++++++++--- src/tree-internal.component.ts | 9 ++++++--- src/tree.types.ts | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/demo/app/app.component.ts b/src/demo/app/app.component.ts index 9d3a088a..487baca8 100644 --- a/src/demo/app/app.component.ts +++ b/src/demo/app/app.component.ts @@ -28,7 +28,7 @@ declare const alertify: any;
-
+
@@ -75,6 +75,7 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { public isRightMenuVisible = false; public isLeftMenuVisible = false; public isChecked = false; + public isReadOnly = false; public controller: TreeController; @@ -94,10 +95,12 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { this.treeService.setController(this.tree.node.id, this.controller); } - this.settings = this.settings || { rootIsVisible: true, enableCheckboxes: false }; + this.settings = this.settings || { rootIsVisible: true, showCheckboxes: false, enableCheckboxes: true }; this.isChecked = this.tree.isChecked; + this.isReadOnly = has(this.settings, 'enableCheckboxes') ? !this.settings.enableCheckboxes : false; + this.subscriptions.push(this.nodeMenuService.hideMenuStream(this.element) .subscribe(() => { this.isRightMenuVisible = false; diff --git a/src/tree.types.ts b/src/tree.types.ts index 7862a1c6..75b0b4e2 100644 --- a/src/tree.types.ts +++ b/src/tree.types.ts @@ -97,6 +97,7 @@ export interface Ng2TreeSettings { * @type boolean */ rootIsVisible?: boolean; + showCheckboxes?: boolean; enableCheckboxes?: boolean; } From 6994152bda55b2051ce880ecd3aef5b61006fb9a Mon Sep 17 00:00:00 2001 From: TMaster Date: Sat, 25 Nov 2017 12:43:06 +0200 Subject: [PATCH 3/5] Fixing merge issues --- src/tree-internal.component.ts | 1 + src/tree.component.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tree-internal.component.ts b/src/tree-internal.component.ts index d70fe039..75c66294 100644 --- a/src/tree-internal.component.ts +++ b/src/tree-internal.component.ts @@ -312,6 +312,7 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { } }); } + } public hasCustomMenu(): boolean { return this.tree.hasCustomMenu(); diff --git a/src/tree.component.ts b/src/tree.component.ts index a6b6c2f2..735ff7e8 100644 --- a/src/tree.component.ts +++ b/src/tree.component.ts @@ -7,8 +7,6 @@ import * as TreeTypes from './tree.types'; import { NodeEvent, NodeCheckedEvent, NodeUncheckedEvent,MenuItemSelectedEvent } from './tree.events'; -import { NodeEvent, MenuItemSelectedEvent } from './tree.events'; - import { Tree } from './tree'; import { TreeController } from './tree-controller'; import { Subscription } from 'rxjs/Subscription'; From 92a96afa835573a97b3f1588b35174b54f75cf65 Mon Sep 17 00:00:00 2001 From: TMaster Date: Sat, 2 Dec 2017 13:02:37 +0200 Subject: [PATCH 4/5] Support indeterminated state --- src/tree-internal.component.ts | 74 +++++++++++++++++++++++++++++++--- src/tree.events.ts | 6 +++ src/tree.service.ts | 8 +++- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/tree-internal.component.ts b/src/tree-internal.component.ts index 75c66294..c450e9ed 100644 --- a/src/tree-internal.component.ts +++ b/src/tree-internal.component.ts @@ -8,7 +8,7 @@ import { OnInit, SimpleChanges, TemplateRef, - ViewChild + ViewChild } from '@angular/core'; import * as TreeTypes from './tree.types'; @@ -17,7 +17,7 @@ import { TreeController } from './tree-controller'; import { NodeMenuService } from './menu/node-menu.service'; import { NodeMenuItemAction, NodeMenuItemSelectedEvent } from './menu/menu.events'; import { NodeEditableEvent, NodeEditableEventAction } from './editable/editable.events'; -import { NodeRemovedEvent, NodeCheckedEvent } from './tree.events' +import { NodeEvent, NodeRemovedEvent, NodeCheckedEvent, NodeIndeterminateEvent } from './tree.events' import { TreeService } from './tree.service'; import * as EventUtils from './utils/event.utils'; import { NodeDraggableEvent } from './draggable/draggable.events'; @@ -104,8 +104,8 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { public constructor(private nodeMenuService: NodeMenuService, - public treeService: TreeService, - public element: ElementRef) { + public treeService: TreeService, + public element: ElementRef) { } public ngOnInit(): void { @@ -140,6 +140,28 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { this.moveNodeToParentTreeAndRemoveFromPreviousOne(e, this.tree); } })); + + this.subscriptions.push(this.treeService.nodeChecked$ + .filter((e: NodeCheckedEvent) => this.eventContainsId(e) && this.tree.children + && this.tree.children.some((child: Tree) => child.id === e.node.id)) + .subscribe((e: NodeCheckedEvent) => { + this.updateIndeterminateState(); + })); + + + this.subscriptions.push(this.treeService.nodeUnchecked$ + .filter((e: NodeCheckedEvent) => this.eventContainsId(e) && this.tree.children + && this.tree.children.some((child: Tree) => child.id === e.node.id)) + .subscribe((e: NodeCheckedEvent) => { + this.updateIndeterminateState(); + })); + + + this.subscriptions.push(this.treeService.nodeIndeterminate$ + .filter((e: NodeIndeterminateEvent) => this.eventContainsId(e) && + this.tree.children && this.tree.children.some((child: Tree) => child.id === e.node.id)) + .subscribe((e: NodeIndeterminateEvent) => { + })); } public ngOnChanges(changes: SimpleChanges): void { @@ -275,6 +297,10 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { return this.tree.isRoot() && !this.settings.rootIsVisible; } + public hasCustomMenu(): boolean { + return this.tree.hasCustomMenu(); + } + public NodeCheckSatusChanged() { if (!this.isChecked) { this.onNodeChecked(); @@ -314,8 +340,44 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { } } - public hasCustomMenu(): boolean { - return this.tree.hasCustomMenu(); + public updateIndeterminateState(): void { + + setTimeout(() => { //calling setTimeout so the value of isChecked will be updated and after that I'll check the children status. + this.updateIndeterminateStateInternal(); + }, 1) + }; + + + private updateIndeterminateStateInternal(): void { + const checkedChildren = this.tree.children.filter(child => child.isChecked).length; + if (checkedChildren === 0) { + this._checkboxElement.nativeElement.indeterminate = false; + this.isChecked = false; + this.tree.isChecked = false; + this.treeService.fireNodeUnchecked(this.tree) + } + else if (checkedChildren === this.tree.children.length) { + this._checkboxElement.nativeElement.indeterminate = false; + this.isChecked = true; + this.tree.isChecked = true; + this.treeService.fireNodeChecked(this.tree) + } + else { + this.setNodeInderminated(); + } + } + + private setNodeInderminated(): void { + this._checkboxElement.nativeElement.indeterminate = true; + this.treeService.fireNodeIndeterminate(this.tree); + } + + private eventContainsId(event: NodeEvent): boolean { + if (!event.node.id) { + console.log('Checking feature requires a well known id for every node, lease prvide a unique id.') + return false; + } + return true; } } diff --git a/src/tree.events.ts b/src/tree.events.ts index d05aae34..f4e317de 100644 --- a/src/tree.events.ts +++ b/src/tree.events.ts @@ -78,3 +78,9 @@ export class NodeUncheckedEvent extends NodeEvent { super(node); } } + +export class NodeIndeterminateEvent extends NodeEvent { + public constructor(node: Tree) { + super(node); + } +} \ No newline at end of file diff --git a/src/tree.service.ts b/src/tree.service.ts index 3844fa2e..7042b090 100644 --- a/src/tree.service.ts +++ b/src/tree.service.ts @@ -9,7 +9,8 @@ import { LoadNextLevelEvent, NodeCheckedEvent, NodeUncheckedEvent, - MenuItemSelectedEvent} from './tree.events'; + MenuItemSelectedEvent, +NodeIndeterminateEvent} from './tree.events'; import { RenamableNode } from './tree.types'; import { Tree } from './tree'; import { TreeController } from './tree-controller'; @@ -33,6 +34,7 @@ export class TreeService { public loadNextLevel$: Subject = new Subject(); public nodeChecked$ : Subject = new Subject(); public nodeUnchecked$ : Subject = new Subject(); + public nodeIndeterminate$ : Subject = new Subject(); private controllers: Map = new Map(); @@ -140,4 +142,8 @@ export class TreeService { return shouldLoadNextLevel; } + + public fireNodeIndeterminate(tree: Tree) : void { + this.nodeIndeterminate$.next(new NodeIndeterminateEvent(tree)); + } } From f62e3b572ee73a9a09991aea463a089107a4646a Mon Sep 17 00:00:00 2001 From: TMaster Date: Sat, 2 Dec 2017 13:08:49 +0200 Subject: [PATCH 5/5] Start comment with a space --- src/tree-internal.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tree-internal.component.ts b/src/tree-internal.component.ts index c450e9ed..d542ba33 100644 --- a/src/tree-internal.component.ts +++ b/src/tree-internal.component.ts @@ -342,7 +342,7 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy { public updateIndeterminateState(): void { - setTimeout(() => { //calling setTimeout so the value of isChecked will be updated and after that I'll check the children status. + setTimeout(() => { // Calling setTimeout so the value of isChecked will be updated and after that I'll check the children status. this.updateIndeterminateStateInternal(); }, 1) };