@c8y/tutorial 1021.2.1 → 1021.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -7
- package/src/grids/server-grid-example/last-updated-data-grid-column/last-updated.cell-renderer.component.ts +10 -0
- package/src/grids/server-grid-example/last-updated-data-grid-column/last-updated.data-grid-column.ts +100 -0
- package/src/grids/server-grid-example/server-grid-example.module.ts +1 -2
- package/src/grids/server-grid-example/server-grid-example.service.ts +18 -41
- package/src/grids/server-grid-example/type-data-grid-column/type.data-grid-column.ts +72 -31
- package/src/grids/server-grid-example/type-data-grid-column/type.filtering-form-renderer.component.ts +5 -11
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1021.
|
|
3
|
+
"version": "1021.4.3",
|
|
4
4
|
"description": "This package is used to scaffold a tutorial for Cumulocity IoT Web SDK which explains a lot of concepts.",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@c8y/style": "1021.
|
|
7
|
-
"@c8y/ngx-components": "1021.
|
|
8
|
-
"@c8y/client": "1021.
|
|
9
|
-
"@c8y/bootstrap": "1021.
|
|
6
|
+
"@c8y/style": "1021.4.3",
|
|
7
|
+
"@c8y/ngx-components": "1021.4.3",
|
|
8
|
+
"@c8y/client": "1021.4.3",
|
|
9
|
+
"@c8y/bootstrap": "1021.4.3",
|
|
10
10
|
"@angular/cdk": "^18.2.10",
|
|
11
11
|
"ngx-bootstrap": "18.0.0",
|
|
12
12
|
"leaflet": "1.9.4",
|
|
13
13
|
"rxjs": "^7.8.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
|
-
"@c8y/options": "1021.
|
|
17
|
-
"@c8y/devkit": "1021.
|
|
16
|
+
"@c8y/options": "1021.4.3",
|
|
17
|
+
"@c8y/devkit": "1021.4.3"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@angular/common": ">=18 <19"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import { CellRendererContext } from '@c8y/ngx-components';
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
template: ` {{ context.value | c8yDate }} `,
|
|
6
|
+
selector: 'c8y-last-updated-cell-renderer'
|
|
7
|
+
})
|
|
8
|
+
export class LastUpdatedCellRendererComponent {
|
|
9
|
+
constructor(public context: CellRendererContext) {}
|
|
10
|
+
}
|
package/src/grids/server-grid-example/last-updated-data-grid-column/last-updated.data-grid-column.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { FormGroup } from '@angular/forms';
|
|
2
|
+
import { BaseColumn, ColumnConfig, gettext } from '@c8y/ngx-components';
|
|
3
|
+
import { LastUpdatedCellRendererComponent } from './last-updated.cell-renderer.component';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Defines a class for custom Last updated date column.
|
|
7
|
+
* Implements `Column` interface and sets basic properties, as well as custom components.
|
|
8
|
+
*/
|
|
9
|
+
export class LastUpdatedDataGridColumn extends BaseColumn {
|
|
10
|
+
constructor(initialColumnConfig?: ColumnConfig) {
|
|
11
|
+
super(initialColumnConfig);
|
|
12
|
+
this.name = 'lastUpdated';
|
|
13
|
+
this.path = 'lastUpdated';
|
|
14
|
+
this.header = 'Last updated';
|
|
15
|
+
|
|
16
|
+
this.cellRendererComponent = LastUpdatedCellRendererComponent;
|
|
17
|
+
|
|
18
|
+
this.filterable = true;
|
|
19
|
+
|
|
20
|
+
this.filteringConfig = {
|
|
21
|
+
/**
|
|
22
|
+
* When using Formly schema, filter chips can be automatically generated by the
|
|
23
|
+
* data-grid library based on the filtering schema definition. You do not need
|
|
24
|
+
* to manually implement the chip generation. The Formly field definitions provided
|
|
25
|
+
* in the `filteringConfig` will automatically be transformed into chips by the
|
|
26
|
+
* data-grid system.
|
|
27
|
+
*/
|
|
28
|
+
fields: [
|
|
29
|
+
{
|
|
30
|
+
type: 'object',
|
|
31
|
+
key: 'lastUpdated',
|
|
32
|
+
templateOptions: {
|
|
33
|
+
label: gettext('Show items last updated')
|
|
34
|
+
},
|
|
35
|
+
fieldGroup: [
|
|
36
|
+
{
|
|
37
|
+
type: 'date-time',
|
|
38
|
+
key: 'after',
|
|
39
|
+
templateOptions: {
|
|
40
|
+
label: gettext('from')
|
|
41
|
+
},
|
|
42
|
+
expressionProperties: {
|
|
43
|
+
'templateOptions.maxDate': (model: any) => model?.before
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'date-time',
|
|
48
|
+
key: 'before',
|
|
49
|
+
templateOptions: {
|
|
50
|
+
label: gettext('to')
|
|
51
|
+
},
|
|
52
|
+
expressionProperties: {
|
|
53
|
+
'templateOptions.minDate': (model: any) => model?.after
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
validators: {
|
|
58
|
+
atLeastOneFilled: {
|
|
59
|
+
expression: (formGroup: any) => {
|
|
60
|
+
const after = formGroup.get('after').value;
|
|
61
|
+
const before = formGroup.get('before').value;
|
|
62
|
+
return after || before;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
formGroup: new FormGroup({}),
|
|
69
|
+
getFilter: model => {
|
|
70
|
+
const filter: any = {};
|
|
71
|
+
const dates = model?.lastUpdated;
|
|
72
|
+
if (dates?.after || dates?.before) {
|
|
73
|
+
filter.__and = [];
|
|
74
|
+
if (dates?.after) {
|
|
75
|
+
const after = this.formatDate(dates.after);
|
|
76
|
+
filter.__and.push({
|
|
77
|
+
'lastUpdated.date': { __gt: after }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (dates?.before) {
|
|
81
|
+
const before = this.formatDate(dates.before);
|
|
82
|
+
filter.__and.push({
|
|
83
|
+
'lastUpdated.date': { __lt: before }
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return filter;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this.sortable = true;
|
|
92
|
+
this.sortingConfig = {
|
|
93
|
+
pathSortingConfigs: [{ path: 'lastUpdated.date' }]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
protected formatDate(dateToFormat: string): string {
|
|
98
|
+
return new Date(dateToFormat).toISOString();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { CommonModule } from '@angular/common';
|
|
2
1
|
import { NgModule } from '@angular/core';
|
|
3
|
-
import { hookNavigator, hookRoute, NavigatorNode } from '@c8y/ngx-components';
|
|
2
|
+
import { CommonModule, hookNavigator, hookRoute, NavigatorNode } from '@c8y/ngx-components';
|
|
4
3
|
import { ServerGridActionControlsModule } from './server-grid-action-controls.module';
|
|
5
4
|
|
|
6
5
|
@NgModule({
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
Pagination
|
|
11
11
|
} from '@c8y/ngx-components';
|
|
12
12
|
|
|
13
|
+
import { assign, get, identity } from 'lodash-es';
|
|
14
|
+
import { LastUpdatedDataGridColumn } from './last-updated-data-grid-column/last-updated.data-grid-column';
|
|
13
15
|
import { TypeDataGridColumn } from './type-data-grid-column/type.data-grid-column';
|
|
14
16
|
|
|
15
17
|
/** Model for custom type filtering form. */
|
|
@@ -59,7 +61,8 @@ export class ServerGridExampleService {
|
|
|
59
61
|
filterable: true,
|
|
60
62
|
sortable: true
|
|
61
63
|
},
|
|
62
|
-
new TypeDataGridColumn()
|
|
64
|
+
new TypeDataGridColumn(),
|
|
65
|
+
new LastUpdatedDataGridColumn()
|
|
63
66
|
];
|
|
64
67
|
|
|
65
68
|
return columns;
|
|
@@ -177,43 +180,6 @@ export class ServerGridExampleService {
|
|
|
177
180
|
return { icon, label };
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
/** Returns a query object for given settings of filtering by type. */
|
|
181
|
-
getTypeQuery(model: TypeFilteringModel): any {
|
|
182
|
-
let query: any = {};
|
|
183
|
-
|
|
184
|
-
if (model.group) {
|
|
185
|
-
query = this.queriesUtil.addOrFilter(query, { type: 'c8y_DeviceGroup' });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (model.device) {
|
|
189
|
-
query = this.queriesUtil.addOrFilter(query, { __has: 'c8y_IsDevice' });
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (model.smartRule) {
|
|
193
|
-
query = this.queriesUtil.addOrFilter(query, {
|
|
194
|
-
type: { __in: ['c8y_SmartRule', 'c8y_PrivateSmartRule'] }
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (model.dashboard) {
|
|
199
|
-
query = this.queriesUtil.addOrFilter(query, {
|
|
200
|
-
type: { __has: 'c8y_Dashboard' }
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (model.file) {
|
|
205
|
-
query = this.queriesUtil.addOrFilter(query, {
|
|
206
|
-
type: { __has: 'c8y_IsBinary' }
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (model.application) {
|
|
211
|
-
query = this.queriesUtil.addOrFilter(query, { type: 'c8y_Application_*' });
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return query;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
183
|
/** Returns filters for given columns and pagination setup. */
|
|
218
184
|
private getFilters(columns: Column[], pagination: Pagination) {
|
|
219
185
|
return {
|
|
@@ -232,10 +198,11 @@ export class ServerGridExampleService {
|
|
|
232
198
|
}
|
|
233
199
|
|
|
234
200
|
/** Returns a query object based on columns setup. */
|
|
235
|
-
private getQueryObj(columns: Column[]): any {
|
|
201
|
+
private getQueryObj(columns: Column[], defaultFilter = {}): any {
|
|
236
202
|
return transform(columns, (query, column) => this.addColumnQuery(query, column), {
|
|
237
203
|
__filter: {},
|
|
238
|
-
__orderby: []
|
|
204
|
+
__orderby: [],
|
|
205
|
+
...defaultFilter
|
|
239
206
|
});
|
|
240
207
|
}
|
|
241
208
|
|
|
@@ -251,7 +218,17 @@ export class ServerGridExampleService {
|
|
|
251
218
|
|
|
252
219
|
// in the case of custom filtering form, we're storing the query in `externalFilterQuery.query`
|
|
253
220
|
if (column.externalFilterQuery) {
|
|
254
|
-
|
|
221
|
+
const getFilter = column.filteringConfig.getFilter || identity;
|
|
222
|
+
const queryObj = getFilter(column.externalFilterQuery);
|
|
223
|
+
|
|
224
|
+
if (queryObj.__or) {
|
|
225
|
+
query.__filter.__and = query.__filter.__and || [];
|
|
226
|
+
query.__filter.__and.push(queryObj);
|
|
227
|
+
} else if (queryObj.__and && get(query, '__filter.__and')) {
|
|
228
|
+
queryObj.__and.map(obj => query.__filter.__and.push(obj));
|
|
229
|
+
} else {
|
|
230
|
+
assign(query.__filter, queryObj);
|
|
231
|
+
}
|
|
255
232
|
}
|
|
256
233
|
}
|
|
257
234
|
|
|
@@ -1,47 +1,88 @@
|
|
|
1
1
|
import { Type } from '@angular/core';
|
|
2
|
-
import {
|
|
3
|
-
import { TypeHeaderCellRendererComponent } from './type.header-cell-renderer.component';
|
|
2
|
+
import { BaseColumn, ColumnConfig, PartialFilterChipGenerationType } from '@c8y/ngx-components';
|
|
4
3
|
import { TypeCellRendererComponent } from './type.cell-renderer.component';
|
|
5
4
|
import { TypeFilteringFormRendererComponent } from './type.filtering-form-renderer.component';
|
|
5
|
+
import { TypeHeaderCellRendererComponent } from './type.header-cell-renderer.component';
|
|
6
|
+
|
|
7
|
+
const FILTER_TYPES = [
|
|
8
|
+
{ key: 'group', label: 'Group' },
|
|
9
|
+
{ key: 'device', label: 'Device' },
|
|
10
|
+
{ key: 'smartRule', label: 'Smart Rule' },
|
|
11
|
+
{ key: 'dashboard', label: 'Dashboard' },
|
|
12
|
+
{ key: 'file', label: 'File' },
|
|
13
|
+
{ key: 'application', label: 'Application' }
|
|
14
|
+
];
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
|
-
* Defines a
|
|
9
|
-
* Implements `Column` interface and sets basic properties, as well as custom components.
|
|
17
|
+
* Defines a custom Type column with custom filtering form and chips generation.
|
|
10
18
|
*/
|
|
11
|
-
export class TypeDataGridColumn
|
|
12
|
-
name
|
|
13
|
-
|
|
14
|
-
header?: string;
|
|
15
|
-
dataType?: ColumnDataType;
|
|
16
|
-
|
|
17
|
-
visible?: boolean;
|
|
18
|
-
positionFixed?: boolean;
|
|
19
|
-
gridTrackSize?: string;
|
|
19
|
+
export class TypeDataGridColumn extends BaseColumn {
|
|
20
|
+
readonly name = 'type';
|
|
21
|
+
readonly header = 'Type';
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
headerCellRendererComponent: Type<any> = TypeHeaderCellRendererComponent;
|
|
24
|
+
cellRendererComponent: Type<any> = TypeCellRendererComponent;
|
|
25
|
+
sortable = false;
|
|
26
|
+
filterable = true;
|
|
27
|
+
filteringFormRendererComponent: Type<any> = TypeFilteringFormRendererComponent;
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
constructor(initialColumnConfig?: ColumnConfig) {
|
|
30
|
+
super(initialColumnConfig);
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
// Set the custom filtering configuration
|
|
33
|
+
this.filteringConfig = {
|
|
34
|
+
/**
|
|
35
|
+
* Generates filter chips based on the selected filters in the model.
|
|
36
|
+
* Each chip represents a filter and provides a way for users to visualize
|
|
37
|
+
* and remove the applied filters.
|
|
38
|
+
*
|
|
39
|
+
* @param model An object with defined structure (e.g. by schema).
|
|
40
|
+
* @returns return an array of partial filter chips with required properties 'displayValue' and 'value'.
|
|
41
|
+
*/
|
|
42
|
+
generateChips: (model): PartialFilterChipGenerationType[] => {
|
|
43
|
+
const chips = [];
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
45
|
+
FILTER_TYPES.forEach(type => {
|
|
46
|
+
if (model[type.key]) {
|
|
47
|
+
chips.push({
|
|
48
|
+
displayValue: type.label,
|
|
49
|
+
value: type.key,
|
|
50
|
+
remove: () => {
|
|
51
|
+
delete model[type.key];
|
|
52
|
+
return {
|
|
53
|
+
externalFilterQuery: { ...model },
|
|
54
|
+
columnName: this.name
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
34
60
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
return chips;
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* Transforms a filtering config model (e.g. coming from schema form component) to a query object.
|
|
65
|
+
* However, using schema form component is not necessary.
|
|
66
|
+
* Model can be defined arbitrarily but must converted to a valid query object.
|
|
67
|
+
* @param model An object with defined structure (e.g. by schema).
|
|
68
|
+
* @returns A query object to be used to generate a query string (QueryUtils).
|
|
69
|
+
*/
|
|
70
|
+
getFilter: model => {
|
|
71
|
+
const filter: any = {};
|
|
72
|
+
const ors = [];
|
|
38
73
|
|
|
39
|
-
|
|
40
|
-
|
|
74
|
+
if (model.group) ors.push({ type: 'c8y_DeviceGroup' });
|
|
75
|
+
if (model.device) ors.push({ __has: 'c8y_IsDevice' });
|
|
76
|
+
if (model.smartRule)
|
|
77
|
+
ors.push({ type: { __in: ['c8y_SmartRule', 'c8y_PrivateSmartRule'] } });
|
|
78
|
+
if (model.dashboard) ors.push({ type: { __has: 'c8y_Dashboard' } });
|
|
79
|
+
if (model.file) ors.push({ type: { __has: 'c8y_IsBinary' } });
|
|
80
|
+
if (model.application) ors.push({ type: 'c8y_Application_*' });
|
|
41
81
|
|
|
42
|
-
|
|
43
|
-
this.filteringFormRendererComponent = TypeFilteringFormRendererComponent;
|
|
82
|
+
if (ors.length) filter.__or = ors;
|
|
44
83
|
|
|
45
|
-
|
|
84
|
+
return filter;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
46
87
|
}
|
|
47
88
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Component
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
2
|
import { FilteringFormRendererContext, FormsModule } from '@c8y/ngx-components';
|
|
3
|
-
import {
|
|
3
|
+
import { TypeFilteringModel } from '../server-grid-example.service';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* This is the example component for custom filtering form.
|
|
@@ -79,12 +79,9 @@ import { ServerGridExampleService, TypeFilteringModel } from '../server-grid-exa
|
|
|
79
79
|
export class TypeFilteringFormRendererComponent {
|
|
80
80
|
model: TypeFilteringModel;
|
|
81
81
|
|
|
82
|
-
constructor(
|
|
83
|
-
public context: FilteringFormRendererContext,
|
|
84
|
-
@Inject(ServerGridExampleService) public service: ServerGridExampleService
|
|
85
|
-
) {
|
|
82
|
+
constructor(public context: FilteringFormRendererContext) {
|
|
86
83
|
// restores the settings from current column setup
|
|
87
|
-
this.model =
|
|
84
|
+
this.model = this.context.property.externalFilterQuery || {};
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
/**
|
|
@@ -94,10 +91,7 @@ export class TypeFilteringFormRendererComponent {
|
|
|
94
91
|
*/
|
|
95
92
|
applyFilter() {
|
|
96
93
|
this.context.applyFilter({
|
|
97
|
-
externalFilterQuery: {
|
|
98
|
-
model: this.model,
|
|
99
|
-
query: this.service.getTypeQuery(this.model)
|
|
100
|
-
}
|
|
94
|
+
externalFilterQuery: { ...this.model }
|
|
101
95
|
});
|
|
102
96
|
}
|
|
103
97
|
|