Skip to content

Commit 7206141

Browse files
Tarjei400Adrian Jutrowski
andauthored
Added add and remove buttons to angular material table renderer (#2021)
* Added add and remove icons to angular material table renderer * Added tests for adding and removing rows functionality Fixes #1782 Co-authored-by: Adrian Jutrowski <[email protected]>
1 parent ac7c8dd commit 7206141

File tree

2 files changed

+133
-11
lines changed

2 files changed

+133
-11
lines changed

packages/angular-material/src/other/table.renderer.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ import {
3030
} from '@jsonforms/angular';
3131
import {
3232
ArrayControlProps,
33-
ControlElement,
33+
ControlElement, createDefaultValue,
3434
deriveTypes,
3535
encode,
3636
isObjectArrayControl,
3737
isPrimitiveArrayControl,
38-
JsonSchema,
38+
JsonSchema, mapDispatchToArrayControlProps,
3939
or,
4040
OwnPropsOfRenderer,
4141
Paths,
@@ -54,6 +54,27 @@ import {
5454
class="mat-elevation-z8"
5555
[trackBy]="trackElement"
5656
>
57+
<ng-container matColumnDef="action">
58+
<tr>
59+
<th mat-header-cell *matHeaderCellDef>
60+
<button mat-button color="primary" (click)="add()" [disabled]="!isEnabled()" matTooltip="Add"><mat-icon>add</mat-icon></button>
61+
</th>
62+
</tr>
63+
<tr>
64+
<td mat-cell *matCellDef="let row; let i = index">
65+
<button
66+
mat-button
67+
color="warn"
68+
(click)="remove(i)"
69+
[disabled]="!isEnabled()"
70+
matTooltipPosition="right"
71+
matTooltip="Delete"
72+
>
73+
<mat-icon>delete</mat-icon>
74+
</button>
75+
</td>
76+
<tr>
77+
</ng-container>
5778
<ng-container
5879
*ngFor="let item of items"
5980
matColumnDef="{{ item.property }}"
@@ -70,13 +91,17 @@ import {
7091
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
7192
</table>
7293
`,
73-
styles: ['table {width: 100%;}']
94+
styles: [
95+
'table {width: 100%;}',
96+
'.cdk-column-action { width: 5%}']
7497
})
7598
export class TableRenderer extends JsonFormsArrayControl {
7699
detailUiSchema: UISchemaElement;
77100
displayedColumns: string[];
78101
items: ColumnDescription[];
79102
readonly columnsToIgnore = ['array', 'object'];
103+
addItem: (path: string, value: any) => () => void;
104+
removeItems: (path: string, toDelete: number[]) => () => void;
80105

81106
constructor(jsonformsService: JsonFormsAngularService) {
82107
super(jsonformsService);
@@ -87,6 +112,9 @@ export class TableRenderer extends JsonFormsArrayControl {
87112
mapAdditionalProps(props: ArrayControlProps) {
88113
this.items = this.generateCells(props.schema, props.path);
89114
this.displayedColumns = this.items.map(item => item.property);
115+
if (this.isEnabled()) {
116+
this.displayedColumns.push('action');
117+
}
90118
}
91119
getProps(index: number, props: OwnPropsOfRenderer): OwnPropsOfRenderer {
92120
const rowPath = Paths.compose(props.path, `${index}`);
@@ -96,6 +124,23 @@ export class TableRenderer extends JsonFormsArrayControl {
96124
path: rowPath
97125
};
98126
}
127+
128+
remove(index: number): void {
129+
this.removeItems(this.propsPath, [index])();
130+
}
131+
add(): void {
132+
this.addItem(this.propsPath, createDefaultValue(this.scopedSchema))();
133+
}
134+
ngOnInit() {
135+
super.ngOnInit();
136+
137+
const { addItem, removeItems } = mapDispatchToArrayControlProps(
138+
this.jsonFormsService.updateCore.bind(this.jsonFormsService)
139+
);
140+
this.addItem = addItem;
141+
this.removeItems = removeItems;
142+
}
143+
99144
generateCells = (
100145
schema: JsonSchema,
101146
rowPath: string

packages/angular-material/test/table-control.spec.ts

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ReactiveFormsModule } from '@angular/forms';
2828
import { MatCardModule } from '@angular/material/card';
2929
import { MatFormFieldModule } from '@angular/material/form-field';
3030
import { MatInputModule } from '@angular/material/input';
31+
import { MatIconModule } from '@angular/material/icon';
3132
import { MatTableModule } from '@angular/material/table';
3233
import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';
3334
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -115,6 +116,7 @@ describe('Table', () => {
115116
MatCardModule,
116117
NoopAnimationsModule,
117118
MatFormFieldModule,
119+
MatIconModule,
118120
MatInputModule,
119121
ReactiveFormsModule,
120122
FlexLayoutModule,
@@ -147,11 +149,11 @@ describe('Table', () => {
147149
component.ngOnInit();
148150
fixture.whenStable().then(() => {
149151
// 2 columns
150-
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(2);
152+
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(3);
151153
// 1 head row and 2 data rows
152154
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 2);
153155
// 4 data entries
154-
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(4);
156+
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(6);
155157
});
156158
}));
157159
it('renders object array on path', async(() => {
@@ -171,11 +173,11 @@ describe('Table', () => {
171173
component.ngOnInit();
172174
fixture.whenStable().then(() => {
173175
// 2 columns
174-
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(2);
176+
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(3);
175177
// 1 head row and 2 data rows
176178
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 2);
177179
// 4 data entries
178-
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(4);
180+
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(6);
179181
});
180182
}));
181183

@@ -190,11 +192,11 @@ describe('Table', () => {
190192
component.ngOnInit();
191193
fixture.whenStable().then(() => {
192194
// 1 column
193-
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(1);
195+
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(2);
194196
// 1 head row and 2 data rows
195197
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 2);
196198
// 2 data entries
197-
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(2);
199+
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(4);
198200
});
199201
}));
200202
it('renders simple array on path', async(() => {
@@ -208,11 +210,11 @@ describe('Table', () => {
208210
component.ngOnInit();
209211
fixture.whenStable().then(() => {
210212
// 1 columns
211-
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(1);
213+
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(2);
212214
// 1 head row and 2 data rows
213215
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 2);
214216
// 2 data entries
215-
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(2);
217+
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(4);
216218
});
217219
}));
218220

@@ -246,8 +248,83 @@ describe('Table', () => {
246248
fixture.detectChanges();
247249
component.ngOnInit();
248250
fixture.whenStable().then(() => {
251+
component.add();
249252
expect(fixture.nativeElement.querySelectorAll('input').length).toBe(2);
250253
expect(fixture.nativeElement.querySelector('input').disabled).toBeFalsy();
251254
});
252255
}));
256+
257+
it('renderer handles removing of rows', async(() => {
258+
setupMockStore(fixture, {
259+
uischema: uischema1,
260+
schema: schema_object1,
261+
data: [
262+
{ foo: 'foo_1', bar: 'bar_1' },
263+
{ foo: 'foo_2', bar: 'bar_2' }
264+
],
265+
renderers
266+
});
267+
268+
fixture.detectChanges();
269+
component.ngOnInit();
270+
component.remove(0);
271+
component.remove(0);
272+
fixture.detectChanges();
273+
274+
fixture.whenStable().then(() => {
275+
276+
// 1 row
277+
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 0);
278+
});
279+
}));
280+
281+
it('renderer handles adding of rows', async(() => {
282+
setupMockStore(fixture, {
283+
uischema: uischema1,
284+
schema: schema_object1,
285+
data: [
286+
{ foo: 'foo_1', bar: 'bar_1' },
287+
{ foo: 'foo_2', bar: 'bar_2' }
288+
],
289+
renderers
290+
});
291+
292+
fixture.detectChanges();
293+
component.ngOnInit();
294+
295+
component.add();
296+
component.add();
297+
fixture.detectChanges();
298+
299+
fixture.whenStable().then(() => {
300+
// 3 row
301+
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 4);
302+
303+
});
304+
}));
305+
306+
it('when disabled doesnt render `add` nor `remove` icons', async(() => {
307+
setupMockStore(fixture, {
308+
uischema: uischema1,
309+
schema: schema_object1,
310+
data: [
311+
{ foo: 'foo_1', bar: 'bar_1' },
312+
{ foo: 'foo_2', bar: 'bar_2' }
313+
],
314+
renderers
315+
});
316+
component.disabled = true;
317+
fixture.detectChanges();
318+
319+
component.ngOnInit();
320+
fixture.whenStable().then(() => {
321+
// 2 columns
322+
expect(fixture.nativeElement.querySelectorAll('th').length).toBe(2);
323+
// 2 rows
324+
expect(fixture.nativeElement.querySelectorAll('tr').length).toBe(1 + 2);
325+
// 2 data entries
326+
expect(fixture.nativeElement.querySelectorAll('td').length).toBe(4);
327+
});
328+
}));
329+
253330
});

0 commit comments

Comments
 (0)