Skip to content

Commit 570f9e4

Browse files
author
Leo Vo
committed
Review feature decouple parent & child check-boxes.
2 parents f4b2b51 + 61f7753 commit 570f9e4

11 files changed

+119
-32
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class AppModule {
7878
hasAllCheckBox: true,
7979
hasFilter: false,
8080
hasCollapseExpand: false,
81+
decoupleChildFromParent: false,
8182
maxHeight: 500
8283
}
8384
```

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ngx-treeview",
3-
"version": "1.2.1",
3+
"version": "1.2.2",
44
"license": "MIT",
55
"description": "An Angular treeview component with checkbox",
66
"scripts": {
@@ -89,7 +89,7 @@
8989
"rimraf": "^2.6.1",
9090
"ts-node": "^3.2.0",
9191
"tslint": "^5.5.0",
92-
"typescript": "^2.4.1",
92+
"typescript": "2.3.2",
9393
"zone.js": "^0.8.1"
9494
}
9595
}

src/demo/book/book.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ <h4>Example 1: Primary features</h4>
2828
hasCollapseExpand
2929
</label>
3030
</div>
31+
<div class="form-check">
32+
<label class="form-check-label">
33+
<input type="checkbox" class="form-check-input" [(ngModel)]="config.decoupleChildFromParent">
34+
decoupleChildFromParent
35+
</label>
36+
</div>
3137
<div class="form-inline">
3238
<div class="form-group">
3339
<label for="maxHeight">maxHeight</label>

src/demo/book/book.component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export class BookComponent implements OnInit {
1616
hasAllCheckBox: true,
1717
hasFilter: true,
1818
hasCollapseExpand: true,
19+
decoupleChildFromParent: false,
1920
maxHeight: 400
2021
});
2122

src/lib/treeview-config.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { TreeviewConfig } from './treeview-config';
22

3-
describe('Treeview-Config', () => {
3+
describe('TreeviewConfig', () => {
44
it('should have sensible default values', () => {
55
const config = new TreeviewConfig();
66
expect(config.hasAllCheckBox).toBe(true);
77
expect(config.hasFilter).toBe(false);
88
expect(config.hasCollapseExpand).toBe(false);
99
expect(config.maxHeight).toBe(500);
10+
expect(config.decoupleChildFromParent).toBe(false);
1011
});
1112
});

src/lib/treeview-config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export class TreeviewConfig {
55
hasAllCheckBox = true;
66
hasFilter = false;
77
hasCollapseExpand = false;
8+
decoupleChildFromParent = false;
89
maxHeight = 500;
910

1011
get hasDivider(): boolean {
@@ -15,7 +16,8 @@ export class TreeviewConfig {
1516
hasAllCheckBox?: boolean,
1617
hasFilter?: boolean,
1718
hasCollapseExpand?: boolean,
18-
maxHeight?: number
19+
decoupleChildFromParent?: boolean
20+
maxHeight?: number,
1921
}): TreeviewConfig {
2022
const config = new TreeviewConfig();
2123
Object.assign(config, fields);

src/lib/treeview-item.component.spec.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Component, DebugElement } from '@angular/core';
2-
import { TestBed, ComponentFixture, fakeAsync, tick, async } from '@angular/core/testing';
1+
import { Component, DebugElement, Injectable } from '@angular/core';
2+
import { TestBed, ComponentFixture, fakeAsync, tick, async, inject } from '@angular/core/testing';
33
import { BrowserModule, By } from '@angular/platform-browser';
44
import { FormsModule } from '@angular/forms';
55
import * as _ from 'lodash';
66
import { expect, createGenericTestComponent } from '../testing';
7+
import { TreeviewConfig } from './treeview-config';
78
import { TreeviewItemComponent } from './treeview-item.component';
89
import { TreeviewItem } from './treeview-item';
910
import { fakeItemTemplate } from './treeview-item-template.spec';
@@ -43,10 +44,17 @@ describe('TreeviewItemComponent', () => {
4344
declarations: [
4445
TestComponent,
4546
TreeviewItemComponent
46-
]
47+
],
48+
providers: [TreeviewConfig]
4749
});
4850
});
4951

52+
it('should initialize with default config', () => {
53+
const defaultConfig = new TreeviewConfig();
54+
const component = TestBed.createComponent(TreeviewItemComponent).componentInstance;
55+
expect(component.config).toEqual(defaultConfig);
56+
});
57+
5058
describe('item', () => {
5159
it('should not have element with class "treeview-item" if no item provided', () => {
5260
const fixture = createTestComponent('<ngx-treeview-item></ngx-treeview-item>');

src/lib/treeview-item.component.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
22
import * as _ from 'lodash';
33
import { TreeviewItem } from './treeview-item';
4+
import { TreeviewConfig } from './treeview-config';
45
import { TreeviewItemTemplateContext } from './treeview-item-template-context';
56

67
@Component({
@@ -9,39 +10,49 @@ import { TreeviewItemTemplateContext } from './treeview-item-template-context';
910
styleUrls: ['./treeview-item.component.scss']
1011
})
1112
export class TreeviewItemComponent {
13+
@Input() config: TreeviewConfig;
1214
@Input() template: TemplateRef<TreeviewItemTemplateContext>;
1315
@Input() item: TreeviewItem;
1416
@Output() checkedChange = new EventEmitter<boolean>();
1517

18+
constructor(
19+
private defaultConfig: TreeviewConfig
20+
) {
21+
this.config = this.defaultConfig;
22+
}
23+
1624
onCollapseExpand = () => {
1725
this.item.collapsed = !this.item.collapsed;
1826
}
1927

2028
onCheckedChange = () => {
2129
const checked = this.item.checked;
22-
if (!_.isNil(this.item.children)) {
30+
if (!_.isNil(this.item.children) && !this.config.decoupleChildFromParent) {
2331
this.item.children.forEach(child => child.setCheckedRecursive(checked));
2432
}
2533
this.checkedChange.emit(checked);
2634
}
2735

2836
onChildCheckedChange(child: TreeviewItem, checked: boolean) {
29-
let itemChecked: boolean = null;
30-
for (const childItem of this.item.children) {
37+
if (!this.config.decoupleChildFromParent) {
38+
let itemChecked: boolean = null;
39+
for (const childItem of this.item.children) {
40+
if (itemChecked === null) {
41+
itemChecked = childItem.checked;
42+
} else if (itemChecked !== childItem.checked) {
43+
itemChecked = undefined;
44+
break;
45+
}
46+
}
47+
3148
if (itemChecked === null) {
32-
itemChecked = childItem.checked;
33-
} else if (itemChecked !== childItem.checked) {
34-
itemChecked = undefined;
35-
break;
49+
itemChecked = false;
3650
}
37-
}
3851

39-
if (itemChecked === null) {
40-
itemChecked = false;
41-
}
52+
if (this.item.checked !== itemChecked) {
53+
this.item.checked = itemChecked;
54+
}
4255

43-
if (this.item.checked !== itemChecked) {
44-
this.item.checked = itemChecked;
4556
}
4657

4758
this.checkedChange.emit(checked);

src/lib/treeview.component.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
</div>
4141
<div [ngSwitch]="hasFilterItems">
4242
<div *ngSwitchCase="true" class="treeview-container" [style.max-height.px]="maxHeight">
43-
<ngx-treeview-item *ngFor="let item of filterItems" [item]="item" [template]="itemTemplate || defaultItemTemplate" (checkedChange)="onItemCheckedChange(item, $event)">
43+
<ngx-treeview-item *ngFor="let item of filterItems" [config]="config" [item]="item" [template]="itemTemplate || defaultItemTemplate"
44+
(checkedChange)="onItemCheckedChange(item, $event)">
4445
</ngx-treeview-item>
4546
</div>
4647
<div *ngSwitchCase="false" class="treeview-text">

src/lib/treeview.component.spec.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ describe('TreeviewComponent', () => {
159159

160160
describe('config', () => {
161161

162-
beforeEach(() => {
163-
fakeData.items = [new TreeviewItem({ text: '1', value: 1 })];
164-
});
165-
166162
describe('hasAllCheckBox', () => {
163+
beforeEach(() => {
164+
fakeData.items = [new TreeviewItem({ text: '1', value: 1 })];
165+
});
166+
167167
it('should display checkbox "All" if value is true', () => {
168168
fakeData.config = TreeviewConfig.create({
169169
hasAllCheckBox: true
@@ -186,6 +186,10 @@ describe('TreeviewComponent', () => {
186186
});
187187

188188
describe('hasFilter', () => {
189+
beforeEach(() => {
190+
fakeData.items = [new TreeviewItem({ text: '1', value: 1 })];
191+
});
192+
189193
it('should display checkbox Filter textbox if value is true', () => {
190194
fakeData.config = TreeviewConfig.create({
191195
hasFilter: true
@@ -208,6 +212,10 @@ describe('TreeviewComponent', () => {
208212
});
209213

210214
describe('hasCollapseExpand', () => {
215+
beforeEach(() => {
216+
fakeData.items = [new TreeviewItem({ text: '1', value: 1 })];
217+
});
218+
211219
it('should display icon Collapse/Expand if value is true', () => {
212220
fakeData.config = TreeviewConfig.create({
213221
hasCollapseExpand: true
@@ -229,8 +237,47 @@ describe('TreeviewComponent', () => {
229237
});
230238
});
231239

240+
describe('decoupleChildFromParent with false value', () => {
241+
let fixture: ComponentFixture<TestComponent>;
242+
let parentCheckbox: DebugElement;
243+
let childCheckbox: DebugElement;
244+
245+
beforeEach(fakeAsync(() => {
246+
fakeData.items = [new TreeviewItem({ text: '1', value: 1, children: [{ text: '11', value: 11 }] })];
247+
fakeData.config = TreeviewConfig.create({
248+
hasAllCheckBox: false,
249+
hasCollapseExpand: false,
250+
hasFilter: false,
251+
decoupleChildFromParent: true
252+
});
253+
fixture = createTestComponent('<ngx-treeview [config]="config" [items]="items"></ngx-treeview>');
254+
fixture.detectChanges();
255+
tick();
256+
const checkboxes = queryItemCheckboxes(fixture.debugElement);
257+
parentCheckbox = checkboxes[0];
258+
childCheckbox = checkboxes[1];
259+
}));
260+
261+
it('should have checked state is true for child item', () => {
262+
expect(childCheckbox.nativeElement.checked).toBeTruthy();
263+
});
264+
265+
describe('uncheck parent', () => {
266+
beforeEach(fakeAsync(() => {
267+
parentCheckbox.nativeElement.click();
268+
fixture.detectChanges();
269+
tick();
270+
}));
271+
272+
it('should not change checked state of child item', () => {
273+
expect(childCheckbox.nativeElement.checked).toBeTruthy();
274+
});
275+
});
276+
});
277+
232278
describe('maxHeight', () => {
233279
it('should display style correct max-height value', () => {
280+
fakeData.items = [new TreeviewItem({ text: '1', value: 1 })];
234281
fakeData.config = TreeviewConfig.create({
235282
maxHeight: 400
236283
});
@@ -242,6 +289,10 @@ describe('TreeviewComponent', () => {
242289
});
243290

244291
describe('divider', () => {
292+
beforeEach(() => {
293+
fakeData.items = [new TreeviewItem({ text: '1', value: 1 })];
294+
});
295+
245296
it('should display divider with default config', () => {
246297
fakeData.config = new TreeviewConfig();
247298
const fixture = createTestComponent('<ngx-treeview [config]="config" [items]="items"></ngx-treeview>');
@@ -316,6 +367,7 @@ describe('TreeviewComponent', () => {
316367
hasAllCheckBox: true,
317368
hasCollapseExpand: true,
318369
hasFilter: true,
370+
decoupleChildFromParent: false,
319371
maxHeight: 400
320372
});
321373
fakeData.items = [

yarn.lock

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
rxjs "^5.4.2"
2626

2727
"@angular/cli@^1.2.0":
28-
version "1.4.2"
29-
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.4.2.tgz#220a54c7e12303157cc289f4306fa46d080705ac"
28+
version "1.4.3"
29+
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.4.3.tgz#8389d4eeadfe34abb1d16e53836416a8f8430fb3"
3030
dependencies:
3131
"@angular-devkit/build-optimizer" "~0.0.18"
3232
"@angular-devkit/schematics" "~0.0.21"
@@ -171,16 +171,16 @@
171171
"@types/jasmine" "*"
172172

173173
"@types/lodash@^4.14.68":
174-
version "4.14.74"
175-
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.74.tgz#ac3bd8db988e7f7038e5d22bd76a7ba13f876168"
174+
version "4.14.76"
175+
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.76.tgz#87874f766774d54e89589697340be9496fb8bf70"
176176

177177
"@types/node@^6.0.46":
178178
version "6.0.88"
179179
resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66"
180180

181181
"@types/node@^8.0.14":
182-
version "8.0.28"
183-
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.28.tgz#86206716f8d9251cf41692e384264cbd7058ad60"
182+
version "8.0.31"
183+
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.31.tgz#d9af61093cf4bfc9f066ca34de0175012cfb0ce9"
184184

185185
"@types/q@^0.0.32":
186186
version "0.0.32"
@@ -6022,7 +6022,11 @@ type-is@~1.6.15:
60226022
media-typer "0.3.0"
60236023
mime-types "~2.1.15"
60246024

6025-
"typescript@>=2.0.0 <2.6.0", typescript@^2.3.3, typescript@^2.4.1:
6025+
6026+
version "2.3.2"
6027+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.2.tgz#f0f045e196f69a72f06b25fd3bd39d01c3ce9984"
6028+
6029+
"typescript@>=2.0.0 <2.6.0", typescript@^2.3.3:
60266030
version "2.5.2"
60276031
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.2.tgz#038a95f7d9bbb420b1bf35ba31d4c5c1dd3ffe34"
60286032

0 commit comments

Comments
 (0)