Skip to content

Commit 7e9c9da

Browse files
guilnorthrafaellmarques
authored andcommitted
feat(tree-view): seleção única e customizada
Permite a função de seleção única utilizando po-radio e a customizar quais itens devem ou não receber seleção. Fixes DTHFUI-7624
1 parent 90e9362 commit 7e9c9da

File tree

11 files changed

+222
-14
lines changed

11 files changed

+222
-14
lines changed

projects/portal/src/assets/json/version.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.spec.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,25 @@ describe('PoTreeViewBaseComponent:', () => {
5353

5454
expectPropertiesValues(component, 'maxLevel', invalidValues, 4);
5555
});
56+
57+
it('p-single-select: should update property with `true` if valid values', () => {
58+
const validValues = [true, 'true', 1, ''];
59+
60+
expectPropertiesValues(component, 'singleSelect', validValues, true);
61+
});
62+
63+
it('p-single-select: should update property with `false` if invalid values', () => {
64+
const invalidValues = [10, 0.5, 'test', undefined];
65+
66+
expectPropertiesValues(component, 'singleSelect', invalidValues, false);
67+
});
5668
});
5769

5870
describe('Methods: ', () => {
71+
beforeEach(() => {
72+
component.singleSelect = false;
73+
});
74+
5975
it('emitExpanded: should call collapsed.emit with tree view item if treeViewItem.expanded is false', () => {
6076
const treeViewItem = { label: 'Nível 01', value: 1, expanded: false };
6177

@@ -100,6 +116,21 @@ describe('PoTreeViewBaseComponent:', () => {
100116
expect(spyUpdateItemsOnSelect).toHaveBeenCalledWith(treeViewItem);
101117
});
102118

119+
it('emitSelected: should emit without treeViewItem.subItems if is `singleSelect`', () => {
120+
const treeViewItem = { label: 'Nível 01', value: 1, selected: true, subItems: [{ label: 'Nivel 02', value: 2 }] };
121+
const expected = { label: 'Nível 01', value: 1, selected: true };
122+
123+
const spyUpdateItemsOnSelect = spyOn(component, <any>'updateItemsOnSelect');
124+
const spySelectedEmit = spyOn(component['selected'], 'emit');
125+
126+
component.singleSelect = true;
127+
component['emitSelected'](treeViewItem);
128+
129+
expect(component.singleSelect).toEqual(true);
130+
expect(spySelectedEmit).toHaveBeenCalledWith(expected);
131+
expect(spyUpdateItemsOnSelect).toHaveBeenCalledWith(expected);
132+
});
133+
103134
it('getItemsByMaxLevel: should return and not call addItem if level is 4', () => {
104135
const items = [];
105136

@@ -216,6 +247,23 @@ describe('PoTreeViewBaseComponent:', () => {
216247
expect(spyExpandParentItem).not.toHaveBeenCalledWith(childItem, parentItem);
217248
});
218249

250+
it('addItem: shouldn`t call selectItemBySubItems if is `singleSelect`', () => {
251+
const childItem = { label: 'Nível 02', value: 2 };
252+
const parentItem = { label: 'Nível 01', value: 1 };
253+
const items = [];
254+
255+
const expectedValue = [parentItem];
256+
257+
const spySelectItemBySubItems = spyOn(component, <any>'selectItemBySubItems');
258+
259+
component.singleSelect = true;
260+
component['addItem'](items, childItem, parentItem);
261+
262+
expect(items.length).toBe(1);
263+
expect(items).toEqual(expectedValue);
264+
expect(spySelectItemBySubItems).not.toHaveBeenCalled();
265+
});
266+
219267
it('addItem: should add parentItem in items and call expandParentItem, addChildItemInParent and selectItemBySubItems', () => {
220268
const childItem = { label: 'Nível 02', value: 2 };
221269
const parentItem = { label: 'Nível 01', value: 1 };
@@ -297,6 +345,26 @@ describe('PoTreeViewBaseComponent:', () => {
297345
expect(spyGetItemsWithParentSelected).toHaveBeenCalledWith(component.items);
298346
});
299347

348+
it('updateItemsOnSelect: shouldn`t call selectAllItems if is singleSelect', () => {
349+
const selectedItem = {
350+
label: 'Label 01',
351+
value: '01',
352+
selected: true,
353+
subItems: [{ label: 'Label 01.1', value: '01.1' }]
354+
};
355+
const items = [selectedItem];
356+
component.items = items;
357+
component.singleSelect = true;
358+
359+
const spyGetItemsWithParentSelected = spyOn(component, <any>'getItemsWithParentSelected').and.returnValue(items);
360+
const spySelect = spyOn(component, <any>'selectAllItems');
361+
362+
component['updateItemsOnSelect'](selectedItem);
363+
364+
expect(spySelect).not.toHaveBeenCalled();
365+
expect(spyGetItemsWithParentSelected).toHaveBeenCalledWith(component.items);
366+
});
367+
300368
it('updateItemsOnSelect: should call selectAllItems if selectedItem has subItems and call getItemsWithParentSelected', () => {
301369
const selectedItem = {
302370
label: 'Label 01',
@@ -305,6 +373,7 @@ describe('PoTreeViewBaseComponent:', () => {
305373
subItems: [{ label: 'Label 01.1', value: '01.1' }]
306374
};
307375
const items = [selectedItem];
376+
308377
component.items = items;
309378

310379
const spyGetItemsWithParentSelected = spyOn(component, <any>'getItemsWithParentSelected').and.returnValue(items);
@@ -370,6 +439,62 @@ describe('PoTreeViewBaseComponent:', () => {
370439
expect(items).toEqual(expectedItems);
371440
});
372441

442+
it('selectAllItems: shouldn`t set `selected` on item if isSelectable is false', () => {
443+
const items = [
444+
{
445+
label: 'Nivel 01',
446+
value: 1,
447+
selected: true,
448+
subItems: [
449+
{
450+
label: 'Nivel 02',
451+
value: 2,
452+
selected: false,
453+
isSelectable: false,
454+
subItems: [
455+
{
456+
label: 'Nivel 03',
457+
value: 3,
458+
selected: false,
459+
subItems: [{ label: 'Nivel 04', value: 4, selected: false }]
460+
}
461+
]
462+
}
463+
]
464+
}
465+
];
466+
467+
const expectedItems = [
468+
{
469+
label: 'Nivel 01',
470+
value: 1,
471+
selected: true,
472+
subItems: [
473+
{
474+
label: 'Nivel 02',
475+
value: 2,
476+
isSelectable: false,
477+
selected: false,
478+
subItems: [
479+
{
480+
label: 'Nivel 03',
481+
value: 3,
482+
selected: true,
483+
subItems: [{ label: 'Nivel 04', value: 4, selected: true }]
484+
}
485+
]
486+
}
487+
]
488+
}
489+
];
490+
491+
const isSelected = true;
492+
493+
component['selectAllItems'](items, isSelected);
494+
495+
expect(items).toEqual(expectedItems);
496+
});
497+
373498
it('selectAllItems: should unselect all items if isSelected is false', () => {
374499
const items = [
375500
{

projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ export class PoTreeViewBaseComponent {
6868
private _items: Array<PoTreeViewItem> = [];
6969
private _selectable: boolean = false;
7070
private _maxLevel = poTreeViewMaxLevel;
71+
private _singleSelect: boolean = false;
72+
73+
// armazena o value do item selecionado
74+
selectedValue: string | number;
7175

7276
/**
7377
* Lista de itens do tipo `PoTreeViewItem` que será renderizada pelo componente.
@@ -99,6 +103,23 @@ export class PoTreeViewBaseComponent {
99103
return this._selectable;
100104
}
101105

106+
/**
107+
* @optional
108+
*
109+
* @description
110+
*
111+
* Habilita a seleção para item único atráves de po-radio.
112+
*
113+
* @default false
114+
*/
115+
@Input('p-single-select') set singleSelect(value: boolean) {
116+
this._singleSelect = convertToBoolean(value);
117+
}
118+
119+
get singleSelect() {
120+
return this._singleSelect;
121+
}
122+
102123
/**
103124
* @optional
104125
*
@@ -127,9 +148,15 @@ export class PoTreeViewBaseComponent {
127148
protected emitSelected(treeViewItem: PoTreeViewItem) {
128149
const event = treeViewItem.selected ? 'selected' : 'unselected';
129150

130-
this.updateItemsOnSelect(treeViewItem);
151+
this.selectedValue = treeViewItem.value;
131152

132-
this[event].emit({ ...treeViewItem });
153+
// Não emitir subItems quando for singleSelect
154+
const { subItems, ...rest } = treeViewItem;
155+
const treeViewToEmit = this.singleSelect ? { ...rest } : treeViewItem;
156+
157+
this.updateItemsOnSelect(treeViewToEmit);
158+
159+
this[event].emit({ ...treeViewToEmit });
133160
}
134161

135162
private addChildItemInParent(childItem: PoTreeViewItem, parentItem: PoTreeViewItem) {
@@ -150,8 +177,12 @@ export class PoTreeViewBaseComponent {
150177
if (isNewItem) {
151178
this.expandParentItem(childItem, parentItem);
152179
}
180+
153181
this.addChildItemInParent(childItem, parentItem);
154-
this.selectItemBySubItems(parentItem);
182+
183+
if (!this.singleSelect) {
184+
this.selectItemBySubItems(parentItem);
185+
}
155186

156187
items.push(parentItem);
157188
} else {
@@ -165,7 +196,7 @@ export class PoTreeViewBaseComponent {
165196
this.selectAllItems(item.subItems, isSelected);
166197
}
167198

168-
item.selected = isSelected;
199+
item.selected = item.isSelectable !== false ? isSelected : false;
169200
});
170201
}
171202

@@ -225,6 +256,10 @@ export class PoTreeViewBaseComponent {
225256
--level;
226257
}
227258

259+
if (item.selected) {
260+
this.selectedValue = currentItem.value;
261+
}
262+
228263
this.addItem(newItems, currentItem, parentItem, true);
229264
});
230265

@@ -246,7 +281,7 @@ export class PoTreeViewBaseComponent {
246281
}
247282

248283
private updateItemsOnSelect(selectedItem: PoTreeViewItem) {
249-
if (selectedItem.subItems) {
284+
if (selectedItem.subItems && !this.singleSelect) {
250285
this.selectAllItems(selectedItem.subItems, selectedItem.selected);
251286
}
252287

projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.html

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</span>
88
</button>
99

10-
<ng-container *ngIf="selectable; then checkboxTemplate; else labelTemplate"></ng-container>
10+
<ng-container *ngIf="selectable; then selectionTemplate; else labelTemplate"></ng-container>
1111
</div>
1212

1313
<ng-template #labelTemplate>
@@ -16,13 +16,34 @@
1616
</span>
1717
</ng-template>
1818

19+
<ng-template #selectionTemplate>
20+
<ng-container *ngIf="singleSelect; then radioTemplate; else checkboxTemplate"></ng-container>
21+
</ng-template>
22+
1923
<ng-template #checkboxTemplate>
2024
<po-checkbox
2125
class="po-tree-view-item-header-checkbox"
2226
[class.po-tree-view-item-header-padding]="!hasSubItems"
2327
[p-label]="item.label"
2428
[(ngModel)]="item.selected"
2529
(p-change)="selected.emit(item)"
30+
[p-disabled]="item.isSelectable === false"
2631
>
2732
</po-checkbox>
2833
</ng-template>
34+
35+
<ng-template #radioTemplate>
36+
<po-radio
37+
class="po-tree-view-item-header-checkbox"
38+
[class.po-tree-view-item-header-padding]="!hasSubItems"
39+
#inputRadio
40+
[name]="idRadio"
41+
[(ngModel)]="item.selected"
42+
[p-label]="item.label"
43+
[p-value]="item.value"
44+
[p-checked]="item.value === selectedValue"
45+
(p-change-selected)="selected.emit(item)"
46+
[p-disabled]="item.isSelectable === false"
47+
>
48+
</po-radio>
49+
</ng-template>

projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core';
2+
import { uuid } from '../../../utils/util';
23

34
import { PoTreeViewItem } from '../po-tree-view-item/po-tree-view-item.interface';
45

@@ -14,10 +15,16 @@ export class PoTreeViewItemHeaderComponent {
1415

1516
@Input('p-selectable') selectable: boolean = false;
1617

18+
@Input('p-single-select') singleSelect: boolean;
19+
1720
@Output('p-expanded') expanded = new EventEmitter<MouseEvent>();
1821

1922
@Output('p-selected') selected = new EventEmitter<any>();
2023

24+
@Input('p-selected-value') selectedValue: string | number;
25+
26+
idRadio = `po-radio[${uuid()}]`;
27+
2128
get hasSubItems() {
2229
return !!(this.item.subItems && this.item.subItems.length);
2330
}

projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<po-tree-view-item-header
33
[p-item]="item"
44
[p-selectable]="selectable"
5+
[p-single-select]="singleSelect"
6+
[p-selected-value]="selectedValue"
57
(p-expanded)="onClick($event)"
68
(p-selected)="onSelect(item)"
79
>
@@ -12,6 +14,8 @@
1214
*ngFor="let subItem of item.subItems; trackBy: trackByFunction"
1315
[p-item]="subItem"
1416
[p-selectable]="selectable"
17+
[p-single-select]="singleSelect"
18+
[p-selected-value]="selectedValue"
1519
>
1620
</po-tree-view-item>
1721
</ul>

projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ export class PoTreeViewItemComponent {
3737

3838
@Input('p-selectable') selectable: boolean;
3939

40+
@Input('p-single-select') singleSelect: boolean;
41+
42+
@Input('p-selected-value') selectedValue: string | number;
43+
4044
get hasSubItems() {
4145
return !!(this.item.subItems && this.item.subItems.length);
4246
}

projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.interface.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export interface PoTreeViewItem {
2525
*/
2626
selected?: boolean | null;
2727

28+
/**
29+
* Permite ativar/desativar a seleção do item
30+
*/
31+
isSelectable?: boolean | null;
32+
2833
/** Lista de itens do próximo nível, e assim consecutivamente até que se atinja o quarto nível. */
2934
subItems?: Array<PoTreeViewItem>;
3035
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<po-container *ngIf="hasItems" p-no-padding>
22
<ul class="po-tree-view">
3-
<po-tree-view-item *ngFor="let item of items; trackBy: trackByFunction" [p-item]="item" [p-selectable]="selectable">
3+
<po-tree-view-item
4+
*ngFor="let item of items; trackBy: trackByFunction"
5+
[p-item]="item"
6+
[p-selectable]="selectable"
7+
[p-single-select]="singleSelect"
8+
[p-selected-value]="selectedValue"
9+
>
410
</po-tree-view-item>
511
</ul>
612
</po-container>

0 commit comments

Comments
 (0)