Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 144 additions & 110 deletions src/demo/app/app.component.ts

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion src/tree-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ export class TreeController {

public startRenaming(): void {
this.tree.markAsBeingRenamed();
}

}
public check() : void {
this.component.onNodeChecked()
}

public uncheck() : void {
this.component.onNodeUnchecked()
}
}
138 changes: 131 additions & 7 deletions src/tree-internal.component.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import {
Component,
ElementRef,
Expand All @@ -6,19 +7,22 @@ import {
OnDestroy,
OnInit,
SimpleChanges,
TemplateRef
TemplateRef,
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 { 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';
import { Subscription } from 'rxjs/Subscription';
import { get } from './utils/fn.utils';
import { get, has } from './utils/fn.utils';

@Component({
selector: 'tree-internal',
Expand All @@ -33,6 +37,11 @@ import { get } from './utils/fn.utils';
[tree]="tree">

<div class="folding" (click)="onSwitchFoldingType()" [ngClass]="tree.foldingCssClass"></div>

<div class="node-checkbox" *ngIf="settings.showCheckboxes">
<input checkbox type="checkbox" [disabled]="isReadOnly" [checked]="isChecked" (change)="NodeCheckSatusChanged()" #checkbox />
</div>

<div class="node-value"
*ngIf="!shouldShowInputForTreeValue()"
[class.node-selected]="isSelected"
Expand All @@ -43,7 +52,7 @@ import { get } from './utils/fn.utils';
<ng-template [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ $implicit: tree.node }"></ng-template>
</div>

<input type="text" class="node-value"
<input type="text" class="node-value"
*ngIf="shouldShowInputForTreeValue()"
[nodeEditable]="tree.value"
(valueChanged)="applyNewValue($event)"/>
Expand All @@ -64,7 +73,7 @@ import { get } from './utils/fn.utils';
(menuItemSelected)="onMenuItemSelected($event)">
</node-menu>
<ng-template [ngIf]="tree.isNodeExpanded()">
<tree-internal *ngFor="let child of tree.childrenAsync | async" [tree]="child" [template]="template"></tree-internal>
<tree-internal *ngFor="let child of tree.childrenAsync | async" [tree]="child" [template]="template" [settings]="settings"></tree-internal>
</ng-template>
</li>
</ul>
Expand All @@ -83,13 +92,20 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy {
public isSelected = false;
public isRightMenuVisible = false;
public isLeftMenuVisible = false;
public isChecked = false;

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.

public isReadOnly = false;
public controller: TreeController;


@ViewChild('checkbox')
_checkboxElement: ElementRef;

This comment was marked as off-topic.


private subscriptions: Subscription[] = [];


public constructor(private nodeMenuService: NodeMenuService,
public treeService: TreeService,
public element: ElementRef) {
public treeService: TreeService,
public element: ElementRef) {

This comment was marked as off-topic.

}

public ngOnInit(): void {
Expand All @@ -98,7 +114,13 @@ 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, showCheckboxes: false, enableCheckboxes: true };

This comment was marked as off-topic.


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;
Expand All @@ -118,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 {
Expand Down Expand Up @@ -256,4 +300,84 @@ export class TreeInternalComponent implements OnInit, OnChanges, OnDestroy {
public hasCustomMenu(): boolean {
return this.tree.hasCustomMenu();
}

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);
}
});
}
}

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 comment was marked as off-topic.

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;
}
}
23 changes: 18 additions & 5 deletions src/tree.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
} from '@angular/core';
import { TreeService } from './tree.service';
import * as TreeTypes from './tree.types';
import { NodeEvent, MenuItemSelectedEvent } from './tree.events';

import { NodeEvent, NodeCheckedEvent, NodeUncheckedEvent,MenuItemSelectedEvent } from './tree.events';

import { Tree } from './tree';
import { TreeController } from './tree-controller';
import { Subscription } from 'rxjs/Subscription';
Expand All @@ -15,7 +17,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')
Expand Down Expand Up @@ -47,19 +49,25 @@ export class TreeComponent implements OnInit, OnChanges, OnDestroy {
public nodeCollapsed: EventEmitter<any> = new EventEmitter();

@Output()
public menuItemSelected: EventEmitter<any> = new EventEmitter();

@Output()
public loadNextLevel: EventEmitter<any> = new EventEmitter();

@Output()
public nodeChecked: EventEmitter<NodeCheckedEvent> = new EventEmitter();

@Output()
public nodeUnchecked: EventEmitter<NodeUncheckedEvent> = new EventEmitter();

public menuItemSelected: EventEmitter<any> = new EventEmitter();

public tree: Tree;
@ViewChild('rootComponent') public rootComponent;

@ContentChild(TemplateRef) public template;

private subscriptions: Subscription[] = [];

public constructor(@Inject(TreeService) private treeService: TreeService) {
public constructor( @Inject(TreeService) private treeService: TreeService) {
}

public ngOnChanges(changes: SimpleChanges): void {
Expand Down Expand Up @@ -106,6 +114,11 @@ export class TreeComponent implements OnInit, OnChanges, OnDestroy {
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 {
Expand Down
18 changes: 18 additions & 0 deletions src/tree.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,21 @@ 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);
}
}

export class NodeIndeterminateEvent extends NodeEvent {
public constructor(node: Tree) {
super(node);
}
}
21 changes: 19 additions & 2 deletions src/tree.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import {
NodeRemovedEvent,
NodeRenamedEvent,
NodeSelectedEvent,
LoadNextLevelEvent,
NodeCheckedEvent,
NodeUncheckedEvent,
MenuItemSelectedEvent,
LoadNextLevelEvent
} from './tree.events';
NodeIndeterminateEvent} from './tree.events';
import { RenamableNode } from './tree.types';
import { Tree } from './tree';
import { TreeController } from './tree-controller';
Expand All @@ -30,6 +32,9 @@ export class TreeService {
public nodeCollapsed$: Subject<NodeCollapsedEvent> = new Subject<NodeCollapsedEvent>();
public menuItemSelected$: Subject<MenuItemSelectedEvent> = new Subject<MenuItemSelectedEvent>();
public loadNextLevel$: Subject<LoadNextLevelEvent> = new Subject<LoadNextLevelEvent>();
public nodeChecked$ : Subject<NodeCheckedEvent> = new Subject<NodeCheckedEvent>();
public nodeUnchecked$ : Subject<NodeUncheckedEvent> = new Subject<NodeUncheckedEvent>();
public nodeIndeterminate$ : Subject<NodeIndeterminateEvent> = new Subject<NodeIndeterminateEvent>();

private controllers: Map<string | number, TreeController> = new Map();

Expand Down Expand Up @@ -88,6 +93,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<NodeDraggableEvent> {
return this.nodeDraggableService.draggableNodeEvents$
.filter((e: NodeDraggableEvent) => e.target === element)
Expand Down Expand Up @@ -129,4 +142,8 @@ export class TreeService {

return shouldLoadNextLevel;
}

public fireNodeIndeterminate(tree: Tree) : void {
this.nodeIndeterminate$.next(new NodeIndeterminateEvent(tree));
}
}
1 change: 1 addition & 0 deletions src/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class Tree {

public node: TreeModel;
public parent: Tree;
public isChecked: boolean;

// STATIC METHODS ----------------------------------------------------------------------------------------------------

Expand Down
3 changes: 3 additions & 0 deletions src/tree.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface TreeModel {
_status?: TreeStatus;
_foldingType?: FoldingType;
[additionalData: string]: any;
checked?: boolean;
}

export interface CssClasses {
Expand Down Expand Up @@ -111,6 +112,8 @@ export interface Ng2TreeSettings {
* @type boolean
*/
rootIsVisible?: boolean;
showCheckboxes?: boolean;
enableCheckboxes?: boolean;
}

export enum TreeStatus {
Expand Down