Skip to content

Commit

Permalink
Merge 74f6fd3 into 8a97848
Browse files Browse the repository at this point in the history
  • Loading branch information
cis-shubham-t committed May 3, 2019
2 parents 8a97848 + 74f6fd3 commit d0157c4
Show file tree
Hide file tree
Showing 20 changed files with 665 additions and 92 deletions.
24 changes: 19 additions & 5 deletions src/app/core-ui/material/material.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@ import {
MatSnackBarModule, MatTabsModule, MatToolbarModule, MatRadioModule, MatInputModule,
MatTooltipModule,
MatSelectModule, MatPaginatorModule, MatProgressSpinnerModule, MatDialogModule,
MatStepperModule, MatSlideToggleModule, MatAutocompleteModule, MatButtonToggleModule
MatStepperModule, MatSlideToggleModule, MatAutocompleteModule, MatButtonToggleModule, MatTreeModule
} from '@angular/material';


import {A11yModule} from '@angular/cdk/a11y';

import { FlexLayoutModule } from '@angular/flex-layout';

import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatSelectSearchComponent } from './mat-select-search/mat-select-search.component';
import { MatOtpGroupSelectSearchComponent } from './mat-otpgroup-select-search/mat-otpgroup-select-search.component';
import { TreeWithSearchComponent } from './tree-with-search/tree-with-search.component';
import { CdkTreeModule } from '@angular/cdk/tree';
import {
TreeWithSearchSingleSelectionComponent
} from './tree-with-search/tree-with-search-single-selection/tree-with-search-single-selection.component';

/* A unified module that will simply manage all our Material imports (and export them again) */

Expand Down Expand Up @@ -54,7 +58,9 @@ import { MatOtpGroupSelectSearchComponent } from './mat-otpgroup-select-search/m
MatStepperModule,
MatSlideToggleModule,
MatAutocompleteModule,
MatButtonToggleModule
MatButtonToggleModule,
MatTreeModule,
CdkTreeModule
],
exports: [
FlexLayoutModule, /* Flex layout here too */
Expand Down Expand Up @@ -86,12 +92,20 @@ import { MatOtpGroupSelectSearchComponent } from './mat-otpgroup-select-search/m
MatSlideToggleModule,
MatAutocompleteModule,
MatButtonToggleModule,
MatTreeModule,
CdkTreeModule,

// custom component.
MatSelectSearchComponent,
MatOtpGroupSelectSearchComponent
MatOtpGroupSelectSearchComponent,
TreeWithSearchComponent,
TreeWithSearchSingleSelectionComponent
],
declarations: [
MatSelectSearchComponent,
MatOtpGroupSelectSearchComponent
MatOtpGroupSelectSearchComponent,
TreeWithSearchComponent,
TreeWithSearchSingleSelectionComponent
]
})
export class MaterialModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';

import { ChecklistDatabaseService } from './checklist-database.service';

describe('ChecklistDatabaseService', () => {
beforeEach(() => TestBed.configureTestingModule({}));

it('should be created', () => {
const service: ChecklistDatabaseService = TestBed.get(ChecklistDatabaseService);
expect(service).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Category } from 'app/core/market/api/category/category.model';
import { ItemNode } from 'app/core-ui/material/tree-with-search/model/item-node';

@Injectable({
providedIn: 'root'
})
export class ChecklistDatabaseService {

dataChange: BehaviorSubject<ItemNode[]> = new BehaviorSubject<ItemNode[]>([]);
treeData: any;
get data(): ItemNode[] { return this.dataChange.value; }

constructor() { }

initialize(TREE_DATA: Category[]) {

this.treeData = TREE_DATA;
// Build the tree nodes from Json object. The result is a list of `ItemNode` with nested
// file node as children.
const data = this.buildCategoryTree(TREE_DATA, 0);

// Notify the change.
this.dataChange.next(data);
}


buildCategoryTree(categories: Category[], level: number): ItemNode[] {
return categories.reduce<ItemNode[]>((accumulator, key) => {
const value = key.subCategoryList;
const node = new ItemNode();
node.item = key.name;
node.id = key.id;

if (value != null && value.length) {
if (Array.isArray(value)) {
node.children = this.buildCategoryTree(value, level + 1);
}
}

return accumulator.concat(node);
}, [])
}

/** Add an item to to-do list */
insertItem(parent: ItemNode, name: string) {
if (parent.children) {
parent.children.push({ item: name } as ItemNode);
this.dataChange.next(this.data);
}
}

updateItem(node: ItemNode, name: string) {
node.item = name;
this.dataChange.next(this.data);
}

public filter(filterText: string) {
let filteredTreeData;
if (filterText) {
filteredTreeData = this.treeData.map(d => {
if (d.name.toLocaleLowerCase().indexOf(filterText.toLocaleLowerCase()) > -1) {
return d;
}
d.subCategoryList = d.subCategoryList.filter((sd) => {
return (sd.name.toLocaleLowerCase().indexOf(filterText.toLocaleLowerCase()) > -1);
})

return d;
}).filter((d) => (d.subCategoryList.length));

} else {

filteredTreeData = this.treeData;
}

// Build the tree nodes from Json object. The result is a list of `ItemNode` with nested
// file node as children.
const data = this.buildCategoryTree(filteredTreeData, 0);
// Notify the change.
this.dataChange.next(data);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ItemFlatNode } from './item-flat-node';

describe('ItemFlatNode', () => {
it('should create an instance', () => {
expect(new ItemFlatNode()).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Flat to-do item node with expandable and level information */
export class ItemFlatNode {
id: number;
item: string;
level: number;
expandable: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ItemNode } from './item-node';

describe('ItemNode', () => {
it('should create an instance', () => {
expect(new ItemNode()).toBeTruthy();
});
});
5 changes: 5 additions & 0 deletions src/app/core-ui/material/tree-with-search/model/item-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ItemNode {
children: ItemNode[];
item: string;
id: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle class="subcategory">
<button mat-icon-button disabled></button>
<mat-checkbox class="checklist-leaf-node" [checked]="checklistSelection.isSelected(node)" (change)="todoLeafItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
</mat-tree-node>

<mat-tree-node *matTreeNodeDef="let node; when: hasChild" class="category">
<button mat-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.filename" class="full-width text-left">
<mat-icon class="part-icon" [ngClass]="{
'part-triangle-down': treeControl.isExpanded(node),
'part-triangle-right': !treeControl.isExpanded(node)
}">
</mat-icon>
{{node.item}}
</button>
</mat-tree-node>
</mat-tree>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { TreeWithSearchSingleSelectionComponent } from './tree-with-search-single-selection.component';
import { CoreUiModule } from 'app/core-ui/core-ui.module';

describe('TreeWithSearchSingleSelectionComponent', () => {
let component: TreeWithSearchSingleSelectionComponent;
let fixture: ComponentFixture<TreeWithSearchSingleSelectionComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
CoreUiModule.forRoot()
]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(TreeWithSearchSingleSelectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { SelectionModel } from '@angular/cdk/collections';
import { FlatTreeControl } from '@angular/cdk/tree';
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
import { Log } from 'ng2-logger';
import { ItemFlatNode } from 'app/core-ui/material/tree-with-search/model/item-flat-node';
import { ItemNode } from 'app/core-ui/material/tree-with-search/model/item-node';
import { ChecklistDatabaseService } from 'app/core-ui/material/tree-with-search/checklist-database.service';

@Component({
selector: 'app-tree-with-search-single-selection',
templateUrl: './tree-with-search-single-selection.component.html',
styleUrls: ['./tree-with-search-single-selection.component.scss']
})
export class TreeWithSearchSingleSelectionComponent implements OnInit {

log: any = Log.create('tree-with-search-single-selection');
@Input() options: any = [];
@Output() onChange: EventEmitter<any> = new EventEmitter<any>();

/** Map from flat node to nested node. This helps us finding the nested node to be modified */
flatNodeMap: any = new Map<ItemFlatNode, ItemNode>();

/** Map from nested node to flattened node. This helps us to keep the same object for selection */
nestedNodeMap: any = new Map<ItemNode, ItemFlatNode>();

/** A selected parent node to be inserted */
selectedParent: ItemFlatNode | null = null;

/** The new item's name */
newItemName: string = '';

treeControl: FlatTreeControl<ItemFlatNode>;

treeFlattener: MatTreeFlattener<ItemNode, ItemFlatNode>;

dataSource: MatTreeFlatDataSource<ItemNode, ItemFlatNode>;

/** The selection for checklist */
checklistSelection: any = new SelectionModel<ItemFlatNode>();

constructor(private database: ChecklistDatabaseService) {
this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel,
this.isExpandable, this.getChildren);
this.treeControl = new FlatTreeControl<ItemFlatNode>(this.getLevel, this.isExpandable);
this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);

database.dataChange.subscribe(data => {
this.dataSource.data = data;
});

}

ngOnInit() {
if (this.options) {
this.database.initialize(this.options);
} else {
this.log.d('category options are not available');
}
}

getLevel = (node: ItemFlatNode) => node.level;

isExpandable = (node: ItemFlatNode) => node.expandable;

getChildren = (node: ItemNode): ItemNode[] => node.children;

hasChild = (_: number, _nodeData: ItemFlatNode) => _nodeData.expandable;

hasNoContent = (_: number, _nodeData: ItemFlatNode) => _nodeData.item === '';

/**
* Transformer to convert nested node to flat node. Record the nodes in maps for later use.
*/
transformer = (node: ItemNode, level: number) => {
const existingNode = this.nestedNodeMap.get(node);
const flatNode = existingNode && existingNode.item === node.item
? existingNode
: new ItemFlatNode();

flatNode.item = node.item;
flatNode.level = level;
flatNode.id = node.id;
flatNode.expandable = !!node.children;
this.flatNodeMap.set(flatNode, node);
this.nestedNodeMap.set(node, flatNode);
return flatNode;
}


/** Toggle a leaf to-do item selection. Check all the parents to see if they changed */
todoLeafItemSelectionToggle(node: ItemFlatNode): void {
this.checklistSelection.toggle(node);

if (this.checklistSelection.isSelected(node)) {
this.onChange.emit(node);
} else {
this.onChange.emit(null);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<mat-form-field>
<input matInput placeholder="Search" (input)="filterChanged($event.target.value)">
</mat-form-field>


<mat-tree [dataSource]="dataSource" [treeControl]="treeControl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle matTreeNodePadding class="subcategory">
<button mat-icon-button disabled></button>
<mat-checkbox class="checklist-leaf-node"
[checked]="checklistSelection.isSelected(node)"
(change)="todoLeafItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
</mat-tree-node>

<mat-tree-node *matTreeNodeDef="let node; when: hasChild" class="category">
<button mat-icon-button matTreeNodeToggle
[attr.aria-label]="'toggle ' + node.filename">
<mat-icon class="part-icon"
[ngClass]="{
'part-triangle-down': treeControl.isExpanded(node),
'part-triangle-right': !treeControl.isExpanded(node)
}">
</mat-icon>
</button>
<mat-checkbox [checked]="descendantsAllSelected(node)"
[indeterminate]="descendantsPartiallySelected(node)"
(change)="todoItemSelectionToggle(node)">{{node.item}}</mat-checkbox>
</mat-tree-node>
</mat-tree>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@import '~@angular/material/prebuilt-themes/deeppurple-amber.css';

body {
font-family: Roboto, Arial, sans-serif;
margin: 0;
}

.basic-container {
padding: 30px;
}

.version-info {
font-size: 8pt;
float: right;
}

0 comments on commit d0157c4

Please sign in to comment.