Skip to content

Commit 58f1f08

Browse files
oburdasovoburdasov
andauthored
feat(tree-select): add search and custom footer (#UIM-635) (#749)
* feat(tree-select): add search and custom footer (#UIM-635) * feat(tree-select): fixes and tests (#UIM-635) * feat(tree-select): code review fixes (#UIM-635) * feat(tree-select): fix focus with search (#UIM-635) * feat(tree-select): another code review fixes (#UIM-635) * feat(tree-select): added docs (#UIM-635) Co-authored-by: oburdasov <[email protected]>
1 parent 7b11139 commit 58f1f08

18 files changed

+692
-25
lines changed

packages/mosaic-dev/tree-select/module.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
/* tslint:disable:no-console no-reserved-keywords */
2-
import { Component, NgModule, ViewEncapsulation } from '@angular/core';
2+
import { Component, NgModule, OnInit, ViewEncapsulation } from '@angular/core';
33
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
44
import { BrowserModule } from '@angular/platform-browser';
55
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
66
import { McButtonModule } from '@ptsecurity/mosaic/button';
7+
import { McHighlightModule } from '@ptsecurity/mosaic/core';
78
import { McFormFieldModule } from '@ptsecurity/mosaic/form-field';
89
import { McIconModule } from '@ptsecurity/mosaic/icon';
910
import { McInputModule } from '@ptsecurity/mosaic/input';
11+
import { McSelectModule } from '@ptsecurity/mosaic/select';
1012
import { McTreeFlatDataSource, McTreeFlattener, FlatTreeControl, McTreeModule } from '@ptsecurity/mosaic/tree';
1113
import { McTreeSelectChange, McTreeSelectModule } from '@ptsecurity/mosaic/tree-select';
1214

@@ -98,7 +100,7 @@ export const DATA_OBJECT = {
98100
styleUrls: ['../main.scss', './styles.scss'],
99101
encapsulation: ViewEncapsulation.None
100102
})
101-
export class DemoComponent {
103+
export class DemoComponent implements OnInit {
102104
disabledState: boolean = false;
103105

104106
control = new FormControl(['Downloads', 'rootNode_1']);
@@ -120,6 +122,8 @@ export class DemoComponent {
120122

121123
multiSelectSelectFormControl = new FormControl([], Validators.pattern(/^w/));
122124

125+
searchControl: FormControl = new FormControl();
126+
123127
constructor() {
124128
this.treeFlattener = new McTreeFlattener(
125129
this.transformer, this.getLevel, this.isExpandable, this.getChildren
@@ -129,10 +133,15 @@ export class DemoComponent {
129133
this.getLevel, this.isExpandable, this.getValue, this.getViewValue
130134
);
131135
this.dataSource = new McTreeFlatDataSource(this.treeControl, this.treeFlattener);
136+
this.dataSource = new McTreeFlatDataSource(this.treeControl, this.treeFlattener);
132137

133138
this.dataSource.data = buildFileTree(DATA_OBJECT, 0);
134139
}
135140

141+
ngOnInit(): void {
142+
this.searchControl.valueChanges.subscribe((value) => this.treeControl.filterNodes(value));
143+
}
144+
136145
hasChild(_: number, nodeData: FileFlatNode) {
137146
return nodeData.expandable;
138147
}
@@ -206,6 +215,8 @@ export class DemoComponent {
206215
FormsModule,
207216
McTreeModule,
208217
McTreeSelectModule,
218+
McSelectModule,
219+
McHighlightModule,
209220

210221
McButtonModule,
211222
McInputModule,

packages/mosaic-dev/tree-select/styles.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@
77

88
padding: 24px;
99
}
10+
11+
.custom-footer {
12+
padding: 10px;
13+
text-align: center;
14+
border-top: 1px solid #B3B3B3;
15+
}

packages/mosaic-dev/tree-select/template.html

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,35 @@
2424
(opened)="opened($event)"
2525
(closed)="closed($event)">
2626

27+
<mc-form-field mcFormFieldWithoutBorders mcSelectSearch>
28+
<i mcPrefix mc-icon="mc-search_16"></i>
29+
<input mcInput [formControl]="searchControl" type="text"/>
30+
<mc-cleaner></mc-cleaner>
31+
</mc-form-field>
32+
33+
<div mc-select-search-empty-result>Ничего не найдено</div>
34+
2735
<mc-tree-selection
2836
[dataSource]="dataSource"
2937
[treeControl]="treeControl">
3038
<mc-tree-option
3139
*mcTreeNodeDef="let node"
3240
mcTreeNodePadding>
33-
{{ treeControl.getViewValue(node) }}
41+
<span [innerHTML]="treeControl.getViewValue(node) | mcHighlight : treeControl.filterValue.value"></span>
3442
</mc-tree-option>
3543

3644
<mc-tree-option
3745
*mcTreeNodeDef="let node; when: hasChild"
3846
mcTreeNodePadding
3947
[disabled]="node.name === 'Downloads'">
4048
<i mc-icon="mc-angle-down-S_16" [style.transform]="treeControl.isExpanded(node) ? '' : 'rotate(-90deg)'" mcTreeNodeToggle></i>
41-
{{ treeControl.getViewValue(node) }}
49+
<span [innerHTML]="treeControl.getViewValue(node) | mcHighlight : treeControl.filterValue.value"></span>
4250
</mc-tree-option>
4351
</mc-tree-selection>
44-
52+
4553
<mc-cleaner #mcSelectCleaner></mc-cleaner>
54+
55+
<div mc-tree-selection-footer class="custom-footer">Custom footer</div>
4656
</mc-tree-select>
4757
</mc-form-field>
4858
</div>

packages/mosaic-examples/mosaic/tree-select/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11
import { CommonModule } from '@angular/common';
22
import { NgModule } from '@angular/core';
33
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
4+
import { McHighlightModule } from '@ptsecurity/mosaic/core';
45
import { McFormFieldModule } from '@ptsecurity/mosaic/form-field';
56
import { McIconModule } from '@ptsecurity/mosaic/icon';
67
import { McInputModule } from '@ptsecurity/mosaic/input';
8+
import { McSelectModule } from '@ptsecurity/mosaic/select';
79
import { McTreeModule } from '@ptsecurity/mosaic/tree';
810
import { McTreeSelectModule } from '@ptsecurity/mosaic/tree-select';
911

12+
import { TreeSelectFooterOverviewExample } from './tree-select-footer-overview/tree-select-footer-overview-example';
1013
import { TreeSelectMultipleOverviewExample } from './tree-select-multiple-overview/tree-select-multiple-overview-example';
1114
import { TreeSelectOverviewExample } from './tree-select-overview/tree-select-overview-example';
15+
import { TreeSelectSearchOverviewExample } from './tree-select-search-overview/tree-select-search-overview-example';
1216

1317

1418
export {
1519
TreeSelectOverviewExample,
16-
TreeSelectMultipleOverviewExample
20+
TreeSelectMultipleOverviewExample,
21+
TreeSelectSearchOverviewExample,
22+
TreeSelectFooterOverviewExample
1723
};
1824

1925
const EXAMPLES = [
2026
TreeSelectOverviewExample,
21-
TreeSelectMultipleOverviewExample
27+
TreeSelectMultipleOverviewExample,
28+
TreeSelectSearchOverviewExample,
29+
TreeSelectFooterOverviewExample
2230
];
2331

2432
@NgModule({
@@ -28,6 +36,8 @@ const EXAMPLES = [
2836
ReactiveFormsModule,
2937
McFormFieldModule,
3038
McTreeModule,
39+
McSelectModule,
40+
McHighlightModule,
3141
McTreeSelectModule,
3242
McInputModule,
3343
McIconModule
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.custom-footer {
2+
padding: 10px;
3+
text-align: center;
4+
border-top: 1px solid #B3B3B3;
5+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<mc-form-field>
2+
<mc-tree-select
3+
[(ngModel)]="selected">
4+
<mc-tree-selection
5+
[dataSource]="dataSource"
6+
[treeControl]="treeControl">
7+
<mc-tree-option *mcTreeNodeDef="let node" mcTreeNodePadding>
8+
{{ treeControl.getViewValue(node) }}
9+
</mc-tree-option>
10+
11+
<mc-tree-option *mcTreeNodeDef="let node; when: hasChild" mcTreeNodePadding>
12+
<i mc-icon="mc-angle-down-S_16"
13+
[style.transform]="treeControl.isExpanded(node) ? '' : 'rotate(-90deg)'"
14+
mcTreeNodeToggle>
15+
</i>
16+
{{ treeControl.getViewValue(node) }}
17+
</mc-tree-option>
18+
</mc-tree-selection>
19+
20+
<mc-cleaner #mcSelectCleaner></mc-cleaner>
21+
22+
<div mc-tree-selection-footer class="custom-footer">Custom footer</div>
23+
</mc-tree-select>
24+
</mc-form-field>
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/* tslint:disable:no-reserved-keywords */
2+
import { Component } from '@angular/core';
3+
import { FlatTreeControl, McTreeFlatDataSource, McTreeFlattener } from '@ptsecurity/mosaic/tree';
4+
5+
6+
export class FileNode {
7+
children: FileNode[];
8+
name: string;
9+
type: any;
10+
}
11+
12+
/** Flat node with expandable and level information */
13+
export class FileFlatNode {
14+
name: string;
15+
type: any;
16+
level: number;
17+
expandable: boolean;
18+
parent: any;
19+
}
20+
21+
/**
22+
* Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object.
23+
* The return value is the list of `FileNode`.
24+
*/
25+
export function buildFileTree(value: any, level: number): FileNode[] {
26+
const data: any[] = [];
27+
28+
for (const k of Object.keys(value)) {
29+
const v = value[k];
30+
const node = new FileNode();
31+
32+
node.name = `${k}`;
33+
34+
if (v === null || v === undefined) {
35+
// no action
36+
} else if (typeof v === 'object') {
37+
node.children = buildFileTree(v, level + 1);
38+
} else {
39+
node.type = v;
40+
}
41+
42+
data.push(node);
43+
}
44+
45+
return data;
46+
}
47+
48+
export const DATA_OBJECT = {
49+
docs: 'app',
50+
src: {
51+
cdk: {
52+
a11ly: {
53+
'aria describer': {
54+
'aria-describer': 'ts',
55+
'aria-describer.spec': 'ts',
56+
'aria-reference': 'ts',
57+
'aria-reference.spec': 'ts'
58+
},
59+
'focus monitor': {
60+
'focus-monitor': 'ts',
61+
'focus-monitor.spec': 'ts'
62+
}
63+
}
64+
},
65+
documentation: {
66+
source: '',
67+
tools: ''
68+
},
69+
mosaic: {
70+
autocomplete: '',
71+
button: '',
72+
'button-toggle': '',
73+
index: 'ts',
74+
package: 'json',
75+
version: 'ts'
76+
},
77+
'mosaic-dev': {
78+
alert: '',
79+
badge: ''
80+
},
81+
'mosaic-examples': '',
82+
'mosaic-moment-adapter': '',
83+
README: 'md',
84+
'tsconfig.build': 'json',
85+
wallabyTest: 'ts'
86+
},
87+
scripts: {
88+
deploy: {
89+
'cleanup-preview': 'ts',
90+
'publish-artifacts': 'sh',
91+
'publish-docs': 'sh',
92+
'publish-docs-preview': 'ts'
93+
},
94+
'tsconfig.deploy': 'json'
95+
},
96+
tests: ''
97+
};
98+
99+
/**
100+
* @title Tree select with footer
101+
*/
102+
@Component({
103+
selector: 'tree-select-footer-overview-example',
104+
templateUrl: 'tree-select-footer-overview-example.html',
105+
styleUrls: ['tree-select-footer-overview-example.css']
106+
})
107+
export class TreeSelectFooterOverviewExample {
108+
selected = '';
109+
110+
treeControl: FlatTreeControl<FileFlatNode>;
111+
treeFlattener: McTreeFlattener<FileNode, FileFlatNode>;
112+
113+
dataSource: McTreeFlatDataSource<FileNode, FileFlatNode>;
114+
115+
constructor() {
116+
this.treeFlattener = new McTreeFlattener(
117+
this.transformer, this.getLevel, this.isExpandable, this.getChildren
118+
);
119+
120+
this.treeControl = new FlatTreeControl<FileFlatNode>(
121+
this.getLevel, this.isExpandable, this.getValue, this.getViewValue
122+
);
123+
this.dataSource = new McTreeFlatDataSource(this.treeControl, this.treeFlattener);
124+
125+
this.dataSource.data = buildFileTree(DATA_OBJECT, 0);
126+
}
127+
128+
hasChild(_: number, nodeData: FileFlatNode) {
129+
return nodeData.expandable;
130+
}
131+
132+
private transformer = (node: FileNode, level: number, parent: any) => {
133+
const flatNode = new FileFlatNode();
134+
135+
flatNode.name = node.name;
136+
flatNode.parent = parent;
137+
flatNode.type = node.type;
138+
flatNode.level = level;
139+
flatNode.expandable = !!node.children;
140+
141+
return flatNode;
142+
}
143+
144+
private getLevel = (node: FileFlatNode) => {
145+
return node.level;
146+
}
147+
148+
private isExpandable = (node: FileFlatNode) => {
149+
return node.expandable;
150+
}
151+
152+
private getChildren = (node: FileNode): FileNode[] => {
153+
return node.children;
154+
}
155+
156+
private getValue = (node: FileNode): string => {
157+
return node.name;
158+
}
159+
160+
private getViewValue = (node: FileNode): string => {
161+
return `${node.name} view`;
162+
}
163+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/** No CSS for this example */
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<mc-form-field>
2+
<mc-tree-select
3+
[multiple]="true"
4+
[(ngModel)]="selected">
5+
6+
<mc-form-field mcFormFieldWithoutBorders mcSelectSearch>
7+
<i mcPrefix mc-icon="mc-search_16"></i>
8+
<input mcInput [formControl]="searchControl" type="text"/>
9+
<mc-cleaner></mc-cleaner>
10+
</mc-form-field>
11+
12+
<div mc-select-search-empty-result>Ничего не найдено</div>
13+
14+
<mc-tree-selection
15+
[dataSource]="dataSource"
16+
[treeControl]="treeControl">
17+
<mc-tree-option *mcTreeNodeDef="let node" mcTreeNodePadding>
18+
<span [innerHTML]="treeControl.getViewValue(node) | mcHighlight : treeControl.filterValue.value"></span>
19+
</mc-tree-option>
20+
21+
<mc-tree-option *mcTreeNodeDef="let node; when: hasChild" mcTreeNodePadding>
22+
<i mc-icon="mc-angle-down-S_16"
23+
[style.transform]="treeControl.isExpanded(node) ? '' : 'rotate(-90deg)'"
24+
mcTreeNodeToggle>
25+
</i>
26+
<span [innerHTML]="treeControl.getViewValue(node) | mcHighlight : treeControl.filterValue.value"></span>
27+
</mc-tree-option>
28+
</mc-tree-selection>
29+
30+
<mc-cleaner #mcSelectCleaner></mc-cleaner>
31+
</mc-tree-select>
32+
</mc-form-field>

0 commit comments

Comments
 (0)