From 7e9c9da049f1d575cb68aeeaffb1933f2700309e Mon Sep 17 00:00:00 2001 From: Guilherme Gomes Noronha Date: Thu, 14 Sep 2023 16:55:01 -0300 Subject: [PATCH] =?UTF-8?q?feat(tree-view):=20sele=C3=A7=C3=A3o=20=C3=BAni?= =?UTF-8?q?ca=20e=20customizada?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- projects/portal/src/assets/json/version.json | 1 - .../po-tree-view-base.component.spec.ts | 125 ++++++++++++++++++ .../po-tree-view-base.component.ts | 45 ++++++- .../po-tree-view-item-header.component.html | 23 +++- .../po-tree-view-item-header.component.ts | 7 + .../po-tree-view-item.component.html | 4 + .../po-tree-view-item.component.ts | 4 + .../po-tree-view-item.interface.ts | 5 + .../po-tree-view/po-tree-view.component.html | 8 +- .../sample-po-tree-view-labs.component.html | 9 +- .../sample-po-tree-view-labs.component.ts | 5 +- 11 files changed, 222 insertions(+), 14 deletions(-) delete mode 100644 projects/portal/src/assets/json/version.json diff --git a/projects/portal/src/assets/json/version.json b/projects/portal/src/assets/json/version.json deleted file mode 100644 index 5a879331f..000000000 --- a/projects/portal/src/assets/json/version.json +++ /dev/null @@ -1 +0,0 @@ -{ "version": "16.0.0-next.0" } diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.spec.ts b/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.spec.ts index efc6aacda..ba7c1e308 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.spec.ts +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.spec.ts @@ -53,9 +53,25 @@ describe('PoTreeViewBaseComponent:', () => { expectPropertiesValues(component, 'maxLevel', invalidValues, 4); }); + + it('p-single-select: should update property with `true` if valid values', () => { + const validValues = [true, 'true', 1, '']; + + expectPropertiesValues(component, 'singleSelect', validValues, true); + }); + + it('p-single-select: should update property with `false` if invalid values', () => { + const invalidValues = [10, 0.5, 'test', undefined]; + + expectPropertiesValues(component, 'singleSelect', invalidValues, false); + }); }); describe('Methods: ', () => { + beforeEach(() => { + component.singleSelect = false; + }); + it('emitExpanded: should call collapsed.emit with tree view item if treeViewItem.expanded is false', () => { const treeViewItem = { label: 'Nível 01', value: 1, expanded: false }; @@ -100,6 +116,21 @@ describe('PoTreeViewBaseComponent:', () => { expect(spyUpdateItemsOnSelect).toHaveBeenCalledWith(treeViewItem); }); + it('emitSelected: should emit without treeViewItem.subItems if is `singleSelect`', () => { + const treeViewItem = { label: 'Nível 01', value: 1, selected: true, subItems: [{ label: 'Nivel 02', value: 2 }] }; + const expected = { label: 'Nível 01', value: 1, selected: true }; + + const spyUpdateItemsOnSelect = spyOn(component, 'updateItemsOnSelect'); + const spySelectedEmit = spyOn(component['selected'], 'emit'); + + component.singleSelect = true; + component['emitSelected'](treeViewItem); + + expect(component.singleSelect).toEqual(true); + expect(spySelectedEmit).toHaveBeenCalledWith(expected); + expect(spyUpdateItemsOnSelect).toHaveBeenCalledWith(expected); + }); + it('getItemsByMaxLevel: should return and not call addItem if level is 4', () => { const items = []; @@ -216,6 +247,23 @@ describe('PoTreeViewBaseComponent:', () => { expect(spyExpandParentItem).not.toHaveBeenCalledWith(childItem, parentItem); }); + it('addItem: shouldn`t call selectItemBySubItems if is `singleSelect`', () => { + const childItem = { label: 'Nível 02', value: 2 }; + const parentItem = { label: 'Nível 01', value: 1 }; + const items = []; + + const expectedValue = [parentItem]; + + const spySelectItemBySubItems = spyOn(component, 'selectItemBySubItems'); + + component.singleSelect = true; + component['addItem'](items, childItem, parentItem); + + expect(items.length).toBe(1); + expect(items).toEqual(expectedValue); + expect(spySelectItemBySubItems).not.toHaveBeenCalled(); + }); + it('addItem: should add parentItem in items and call expandParentItem, addChildItemInParent and selectItemBySubItems', () => { const childItem = { label: 'Nível 02', value: 2 }; const parentItem = { label: 'Nível 01', value: 1 }; @@ -297,6 +345,26 @@ describe('PoTreeViewBaseComponent:', () => { expect(spyGetItemsWithParentSelected).toHaveBeenCalledWith(component.items); }); + it('updateItemsOnSelect: shouldn`t call selectAllItems if is singleSelect', () => { + const selectedItem = { + label: 'Label 01', + value: '01', + selected: true, + subItems: [{ label: 'Label 01.1', value: '01.1' }] + }; + const items = [selectedItem]; + component.items = items; + component.singleSelect = true; + + const spyGetItemsWithParentSelected = spyOn(component, 'getItemsWithParentSelected').and.returnValue(items); + const spySelect = spyOn(component, 'selectAllItems'); + + component['updateItemsOnSelect'](selectedItem); + + expect(spySelect).not.toHaveBeenCalled(); + expect(spyGetItemsWithParentSelected).toHaveBeenCalledWith(component.items); + }); + it('updateItemsOnSelect: should call selectAllItems if selectedItem has subItems and call getItemsWithParentSelected', () => { const selectedItem = { label: 'Label 01', @@ -305,6 +373,7 @@ describe('PoTreeViewBaseComponent:', () => { subItems: [{ label: 'Label 01.1', value: '01.1' }] }; const items = [selectedItem]; + component.items = items; const spyGetItemsWithParentSelected = spyOn(component, 'getItemsWithParentSelected').and.returnValue(items); @@ -370,6 +439,62 @@ describe('PoTreeViewBaseComponent:', () => { expect(items).toEqual(expectedItems); }); + it('selectAllItems: shouldn`t set `selected` on item if isSelectable is false', () => { + const items = [ + { + label: 'Nivel 01', + value: 1, + selected: true, + subItems: [ + { + label: 'Nivel 02', + value: 2, + selected: false, + isSelectable: false, + subItems: [ + { + label: 'Nivel 03', + value: 3, + selected: false, + subItems: [{ label: 'Nivel 04', value: 4, selected: false }] + } + ] + } + ] + } + ]; + + const expectedItems = [ + { + label: 'Nivel 01', + value: 1, + selected: true, + subItems: [ + { + label: 'Nivel 02', + value: 2, + isSelectable: false, + selected: false, + subItems: [ + { + label: 'Nivel 03', + value: 3, + selected: true, + subItems: [{ label: 'Nivel 04', value: 4, selected: true }] + } + ] + } + ] + } + ]; + + const isSelected = true; + + component['selectAllItems'](items, isSelected); + + expect(items).toEqual(expectedItems); + }); + it('selectAllItems: should unselect all items if isSelected is false', () => { const items = [ { diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.ts b/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.ts index a97d6fb46..5dfdbe46a 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.ts +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-base.component.ts @@ -68,6 +68,10 @@ export class PoTreeViewBaseComponent { private _items: Array = []; private _selectable: boolean = false; private _maxLevel = poTreeViewMaxLevel; + private _singleSelect: boolean = false; + + // armazena o value do item selecionado + selectedValue: string | number; /** * Lista de itens do tipo `PoTreeViewItem` que será renderizada pelo componente. @@ -99,6 +103,23 @@ export class PoTreeViewBaseComponent { return this._selectable; } + /** + * @optional + * + * @description + * + * Habilita a seleção para item único atráves de po-radio. + * + * @default false + */ + @Input('p-single-select') set singleSelect(value: boolean) { + this._singleSelect = convertToBoolean(value); + } + + get singleSelect() { + return this._singleSelect; + } + /** * @optional * @@ -127,9 +148,15 @@ export class PoTreeViewBaseComponent { protected emitSelected(treeViewItem: PoTreeViewItem) { const event = treeViewItem.selected ? 'selected' : 'unselected'; - this.updateItemsOnSelect(treeViewItem); + this.selectedValue = treeViewItem.value; - this[event].emit({ ...treeViewItem }); + // Não emitir subItems quando for singleSelect + const { subItems, ...rest } = treeViewItem; + const treeViewToEmit = this.singleSelect ? { ...rest } : treeViewItem; + + this.updateItemsOnSelect(treeViewToEmit); + + this[event].emit({ ...treeViewToEmit }); } private addChildItemInParent(childItem: PoTreeViewItem, parentItem: PoTreeViewItem) { @@ -150,8 +177,12 @@ export class PoTreeViewBaseComponent { if (isNewItem) { this.expandParentItem(childItem, parentItem); } + this.addChildItemInParent(childItem, parentItem); - this.selectItemBySubItems(parentItem); + + if (!this.singleSelect) { + this.selectItemBySubItems(parentItem); + } items.push(parentItem); } else { @@ -165,7 +196,7 @@ export class PoTreeViewBaseComponent { this.selectAllItems(item.subItems, isSelected); } - item.selected = isSelected; + item.selected = item.isSelectable !== false ? isSelected : false; }); } @@ -225,6 +256,10 @@ export class PoTreeViewBaseComponent { --level; } + if (item.selected) { + this.selectedValue = currentItem.value; + } + this.addItem(newItems, currentItem, parentItem, true); }); @@ -246,7 +281,7 @@ export class PoTreeViewBaseComponent { } private updateItemsOnSelect(selectedItem: PoTreeViewItem) { - if (selectedItem.subItems) { + if (selectedItem.subItems && !this.singleSelect) { this.selectAllItems(selectedItem.subItems, selectedItem.selected); } diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.html b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.html index 833f6bed8..81d1f7e96 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.html +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.html @@ -7,7 +7,7 @@ - + @@ -16,6 +16,10 @@ + + + + + + + + + diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.ts b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.ts index 0670af589..6ad25a003 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.ts +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item-header/po-tree-view-item-header.component.ts @@ -1,4 +1,5 @@ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { uuid } from '../../../utils/util'; import { PoTreeViewItem } from '../po-tree-view-item/po-tree-view-item.interface'; @@ -14,10 +15,16 @@ export class PoTreeViewItemHeaderComponent { @Input('p-selectable') selectable: boolean = false; + @Input('p-single-select') singleSelect: boolean; + @Output('p-expanded') expanded = new EventEmitter(); @Output('p-selected') selected = new EventEmitter(); + @Input('p-selected-value') selectedValue: string | number; + + idRadio = `po-radio[${uuid()}]`; + get hasSubItems() { return !!(this.item.subItems && this.item.subItems.length); } diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.html b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.html index 5bd1da7c4..58dbe213c 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.html +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.html @@ -2,6 +2,8 @@ @@ -12,6 +14,8 @@ *ngFor="let subItem of item.subItems; trackBy: trackByFunction" [p-item]="subItem" [p-selectable]="selectable" + [p-single-select]="singleSelect" + [p-selected-value]="selectedValue" > diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.ts b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.ts index 0539d6804..edc4fbb00 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.ts +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.component.ts @@ -37,6 +37,10 @@ export class PoTreeViewItemComponent { @Input('p-selectable') selectable: boolean; + @Input('p-single-select') singleSelect: boolean; + + @Input('p-selected-value') selectedValue: string | number; + get hasSubItems() { return !!(this.item.subItems && this.item.subItems.length); } diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.interface.ts b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.interface.ts index 5e9143d2e..0e3c0c612 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.interface.ts +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view-item/po-tree-view-item.interface.ts @@ -25,6 +25,11 @@ export interface PoTreeViewItem { */ selected?: boolean | null; + /** + * Permite ativar/desativar a seleção do item + */ + isSelectable?: boolean | null; + /** Lista de itens do próximo nível, e assim consecutivamente até que se atinja o quarto nível. */ subItems?: Array; } diff --git a/projects/ui/src/lib/components/po-tree-view/po-tree-view.component.html b/projects/ui/src/lib/components/po-tree-view/po-tree-view.component.html index afb7d2d4f..c32b36fbe 100644 --- a/projects/ui/src/lib/components/po-tree-view/po-tree-view.component.html +++ b/projects/ui/src/lib/components/po-tree-view/po-tree-view.component.html @@ -1,6 +1,12 @@
    - +
diff --git a/projects/ui/src/lib/components/po-tree-view/samples/sample-po-tree-view-labs/sample-po-tree-view-labs.component.html b/projects/ui/src/lib/components/po-tree-view/samples/sample-po-tree-view-labs/sample-po-tree-view-labs.component.html index f6b50b04e..7f5f327ed 100644 --- a/projects/ui/src/lib/components/po-tree-view/samples/sample-po-tree-view-labs/sample-po-tree-view-labs.component.html +++ b/projects/ui/src/lib/components/po-tree-view/samples/sample-po-tree-view-labs/sample-po-tree-view-labs.component.html @@ -6,6 +6,7 @@ (p-selected)="changeEvent('p-selected', $event)" (p-unselected)="changeEvent('p-unselected', $event)" [p-max-level]="maxLevel" + [p-single-select]="singleSelect" > @@ -15,14 +16,12 @@ -
- -
-
+ +
@@ -46,7 +45,7 @@
= [ { value: 'selected', label: 'Selected' }, - { value: 'expanded', label: 'Expanded' } + { value: 'expanded', label: 'Expanded' }, + { value: 'disable-selection', label: 'Disable Selection' } ]; ngOnInit() { @@ -28,6 +30,7 @@ export class SamplePoTreeViewLabsComponent implements OnInit { add(treeViewItem: PoTreeViewItem) { treeViewItem.selected = this.itemProperties.includes('selected'); treeViewItem.expanded = this.itemProperties.includes('expanded'); + treeViewItem.isSelectable = !this.itemProperties.includes('disable-selection'); const treeViewItemClone = { ...treeViewItem };