@c8y/tutorial 1023.71.1 → 1023.76.0
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/cumulocity.config.ts +7 -0
- package/package.json +8 -8
- package/src/generate-json-schema/generate-json-schema.component.ts +1 -1
- package/src/global-context-widget/index.ts +4 -1
- package/src/input-group/input-group-editable-example.component.ts +263 -0
- package/src/input-group/input-group-editable-example.module.ts +25 -0
- package/src/lazy-widget/index.ts +4 -1
- package/src/widget/index.ts +5 -1
- package/src/widget-resolvers/index.ts +4 -1
package/cumulocity.config.ts
CHANGED
|
@@ -647,6 +647,13 @@ export default {
|
|
|
647
647
|
description: 'An example for extendable input list.',
|
|
648
648
|
scope: 'self'
|
|
649
649
|
},
|
|
650
|
+
{
|
|
651
|
+
name: 'Example of inline editable input',
|
|
652
|
+
module: 'InputGroupEditableExampleModule',
|
|
653
|
+
path: './src/input-group/input-group-editable-example.module.ts',
|
|
654
|
+
description: 'An example for the input-group-editable pattern.',
|
|
655
|
+
scope: 'self'
|
|
656
|
+
},
|
|
650
657
|
{
|
|
651
658
|
name: 'Example of time picker',
|
|
652
659
|
module: 'TimePickerExampleModule',
|
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c8y/tutorial",
|
|
3
|
-
"version": "1023.
|
|
3
|
+
"version": "1023.76.0",
|
|
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": "1023.71.1",
|
|
7
|
-
"@c8y/ngx-components": "1023.71.1",
|
|
8
|
-
"@c8y/client": "1023.71.1",
|
|
9
|
-
"@c8y/bootstrap": "1023.71.1",
|
|
10
6
|
"@angular/cdk": "^20.2.14",
|
|
7
|
+
"@c8y/bootstrap": "1023.76.0",
|
|
8
|
+
"@c8y/client": "1023.76.0",
|
|
9
|
+
"@c8y/ngx-components": "1023.76.0",
|
|
10
|
+
"@c8y/style": "1023.76.0",
|
|
11
|
+
"leaflet": "1.9.4",
|
|
11
12
|
"monaco-editor": "~0.53.0",
|
|
12
13
|
"ngx-bootstrap": "20.0.2",
|
|
13
|
-
"leaflet": "1.9.4",
|
|
14
14
|
"rxjs": "7.8.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@c8y/
|
|
18
|
-
"@c8y/
|
|
17
|
+
"@c8y/devkit": "1023.76.0",
|
|
18
|
+
"@c8y/options": "1023.76.0"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"@angular/common": ">=20 <21"
|
|
@@ -38,7 +38,7 @@ export class GenerateJsonSchemaComponent implements OnInit {
|
|
|
38
38
|
|
|
39
39
|
async ngOnInit() {
|
|
40
40
|
const { schema } = await import(
|
|
41
|
-
'c8y-schema-loader?interfaceName=ExampleInterface!./schema-example.model'
|
|
41
|
+
'c8y-schema-loader?interfaceName=ExampleInterface&type=some-type-to-be-grouped-by!./schema-example.model'
|
|
42
42
|
);
|
|
43
43
|
this.schemaString = JSON.stringify(schema, null, 2);
|
|
44
44
|
}
|
|
@@ -47,7 +47,10 @@ export function provideGlobalContextWidget() {
|
|
|
47
47
|
m => m.GlobalContextWidgetConfigComponent
|
|
48
48
|
),
|
|
49
49
|
data: {
|
|
50
|
-
schema: () =>
|
|
50
|
+
schema: () =>
|
|
51
|
+
import(
|
|
52
|
+
'c8y-schema-loader?interfaceName=WidgetConfig&type=widget-config!./widget-config.model'
|
|
53
|
+
),
|
|
51
54
|
// Widget controls for view component
|
|
52
55
|
controls: WIDGET_CONTROLS
|
|
53
56
|
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { Component } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
AbstractControl,
|
|
4
|
+
AsyncValidatorFn,
|
|
5
|
+
FormControl,
|
|
6
|
+
ValidationErrors,
|
|
7
|
+
ValidatorFn,
|
|
8
|
+
Validators
|
|
9
|
+
} from '@angular/forms';
|
|
10
|
+
import { CoreModule, InputGroupEditableComponent } from '@c8y/ngx-components';
|
|
11
|
+
import { Observable, map, timer } from 'rxjs';
|
|
12
|
+
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'c8y-input-group-editable-example',
|
|
15
|
+
template: `
|
|
16
|
+
<c8y-title>Inline editable input</c8y-title>
|
|
17
|
+
<div class="container-fluid p-24">
|
|
18
|
+
<!-- SIZES -->
|
|
19
|
+
<h4 class="text-medium m-b-24">Sizes</h4>
|
|
20
|
+
<div class="row m-b-24">
|
|
21
|
+
<div class="col-sm-4">
|
|
22
|
+
<p class="text-label-small">Default</p>
|
|
23
|
+
<c8y-input-group-editable
|
|
24
|
+
ariaLabel="Default size"
|
|
25
|
+
[(ngModel)]="sizeDefault"
|
|
26
|
+
></c8y-input-group-editable>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="col-sm-4">
|
|
29
|
+
<p class="text-label-small">Small (size="sm")</p>
|
|
30
|
+
<c8y-input-group-editable
|
|
31
|
+
size="sm"
|
|
32
|
+
ariaLabel="Small size"
|
|
33
|
+
[(ngModel)]="sizeSm"
|
|
34
|
+
></c8y-input-group-editable>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="col-sm-4">
|
|
37
|
+
<p class="text-label-small">Large (size="lg")</p>
|
|
38
|
+
<c8y-input-group-editable
|
|
39
|
+
size="lg"
|
|
40
|
+
ariaLabel="Large size"
|
|
41
|
+
[(ngModel)]="sizeLg"
|
|
42
|
+
></c8y-input-group-editable>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<hr />
|
|
47
|
+
|
|
48
|
+
<!-- BINDING PATTERNS -->
|
|
49
|
+
<h4 class="text-medium m-b-24">Binding patterns</h4>
|
|
50
|
+
<div class="row m-b-24">
|
|
51
|
+
<div class="col-sm-4">
|
|
52
|
+
<p class="text-label-small">Two-way [(ngModel)]</p>
|
|
53
|
+
<c8y-input-group-editable
|
|
54
|
+
ariaLabel="Two-way ngModel"
|
|
55
|
+
[(ngModel)]="bindingTwoWay"
|
|
56
|
+
></c8y-input-group-editable>
|
|
57
|
+
<small class="text-muted">Value: {{ bindingTwoWay }}</small>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="col-sm-4">
|
|
60
|
+
<p class="text-label-small">[ngModel] and (ngModelChange)</p>
|
|
61
|
+
<c8y-input-group-editable
|
|
62
|
+
ariaLabel="One-way ngModel with ngModelChange"
|
|
63
|
+
[ngModel]="bindingOneWay"
|
|
64
|
+
(ngModelChange)="bindingOneWay = $event"
|
|
65
|
+
></c8y-input-group-editable>
|
|
66
|
+
<small class="text-muted">Value: {{ bindingOneWay }}</small>
|
|
67
|
+
</div>
|
|
68
|
+
<div class="col-sm-4">
|
|
69
|
+
<p class="text-label-small">[formControl]</p>
|
|
70
|
+
<c8y-input-group-editable
|
|
71
|
+
ariaLabel="Form control binding"
|
|
72
|
+
[formControl]="bindingFormControl"
|
|
73
|
+
></c8y-input-group-editable>
|
|
74
|
+
<small class="text-muted">Value: {{ bindingFormControl.value }}</small>
|
|
75
|
+
<p class="text-muted m-t-4">
|
|
76
|
+
<small
|
|
77
|
+
>Use <code>formControlName</code> the same way inside a <code>FormGroup</code>.</small
|
|
78
|
+
>
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<hr />
|
|
84
|
+
|
|
85
|
+
<!-- OUTPUTS -->
|
|
86
|
+
<h4 class="text-medium m-b-24">Outputs — (save) and (cancel)</h4>
|
|
87
|
+
<div class="row m-b-24">
|
|
88
|
+
<div class="col-sm-6">
|
|
89
|
+
<p class="text-label-small">Tracking last action alongside [(ngModel)]</p>
|
|
90
|
+
<c8y-input-group-editable
|
|
91
|
+
ariaLabel="Field with save and cancel outputs"
|
|
92
|
+
[(ngModel)]="outputsModel"
|
|
93
|
+
(save)="onSave($event)"
|
|
94
|
+
(cancel)="onCancel()"
|
|
95
|
+
></c8y-input-group-editable>
|
|
96
|
+
<div class="m-t-8">
|
|
97
|
+
<small class="d-block text-muted"
|
|
98
|
+
>Last action: <strong>{{ lastAction || '—' }}</strong></small
|
|
99
|
+
>
|
|
100
|
+
<small class="d-block text-muted"
|
|
101
|
+
>Last saved value: <strong>{{ lastSavedValue || '—' }}</strong></small
|
|
102
|
+
>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<hr />
|
|
108
|
+
|
|
109
|
+
<!-- PLACEHOLDER AND NAME -->
|
|
110
|
+
<h4 class="text-medium m-b-24">Placeholder and name</h4>
|
|
111
|
+
<div class="row m-b-24">
|
|
112
|
+
<div class="col-sm-4">
|
|
113
|
+
<p class="text-label-small">Empty field with placeholder</p>
|
|
114
|
+
<c8y-input-group-editable
|
|
115
|
+
placeholder="e.g. My device"
|
|
116
|
+
name="device-name"
|
|
117
|
+
ariaLabel="Device name"
|
|
118
|
+
[(ngModel)]="placeholderModel"
|
|
119
|
+
></c8y-input-group-editable>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<hr />
|
|
124
|
+
|
|
125
|
+
<!-- SYNC VALIDATION -->
|
|
126
|
+
<h4 class="text-medium m-b-24">Validation — synchronous</h4>
|
|
127
|
+
<div class="row m-b-24">
|
|
128
|
+
<div class="col-sm-4">
|
|
129
|
+
<p class="text-label-small">Template-driven (required, minlength)</p>
|
|
130
|
+
<c8y-input-group-editable
|
|
131
|
+
required
|
|
132
|
+
minlength="3"
|
|
133
|
+
ariaLabel="Template-driven validation"
|
|
134
|
+
[(ngModel)]="validTplModel"
|
|
135
|
+
></c8y-input-group-editable>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="col-sm-4">
|
|
138
|
+
<p class="text-label-small">[validators] input — no spaces</p>
|
|
139
|
+
<c8y-input-group-editable
|
|
140
|
+
ariaLabel="Validators input"
|
|
141
|
+
[(ngModel)]="validatorsModel"
|
|
142
|
+
[validators]="noSpacesValidator"
|
|
143
|
+
>
|
|
144
|
+
<c8y-message
|
|
145
|
+
name="noSpaces"
|
|
146
|
+
[text]="'Spaces are not allowed.' | translate"
|
|
147
|
+
></c8y-message>
|
|
148
|
+
</c8y-input-group-editable>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="col-sm-4">
|
|
151
|
+
<p class="text-label-small">[formControl] and <c8y-message></p>
|
|
152
|
+
<c8y-input-group-editable
|
|
153
|
+
ariaLabel="FormControl with custom messages"
|
|
154
|
+
[formControl]="validFormControl"
|
|
155
|
+
>
|
|
156
|
+
<c8y-message
|
|
157
|
+
name="required"
|
|
158
|
+
[text]="'This field is required.' | translate"
|
|
159
|
+
></c8y-message>
|
|
160
|
+
<c8y-message
|
|
161
|
+
name="minlength"
|
|
162
|
+
[text]="'Minimum 3 characters required.' | translate"
|
|
163
|
+
></c8y-message>
|
|
164
|
+
</c8y-input-group-editable>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
<hr />
|
|
169
|
+
|
|
170
|
+
<!-- ASYNC VALIDATION -->
|
|
171
|
+
<h4 class="text-medium m-b-24">Validation — asynchronous</h4>
|
|
172
|
+
<div class="row m-b-24">
|
|
173
|
+
<div class="col-sm-4">
|
|
174
|
+
<p class="text-label-small">[asyncValidators] — try typing "taken"</p>
|
|
175
|
+
<c8y-input-group-editable
|
|
176
|
+
ariaLabel="Async uniqueness validation"
|
|
177
|
+
[formControl]="asyncFormControl"
|
|
178
|
+
[asyncValidators]="uniquenessValidator"
|
|
179
|
+
>
|
|
180
|
+
<c8y-message
|
|
181
|
+
name="notUnique"
|
|
182
|
+
[text]="'This name is already taken.' | translate"
|
|
183
|
+
></c8y-message>
|
|
184
|
+
</c8y-input-group-editable>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<hr />
|
|
189
|
+
|
|
190
|
+
<!-- DISABLED STATE -->
|
|
191
|
+
<h4 class="text-medium m-b-24">Disabled state</h4>
|
|
192
|
+
<div class="row m-b-24">
|
|
193
|
+
<div class="col-sm-4">
|
|
194
|
+
<p class="text-label-small">disabled attribute</p>
|
|
195
|
+
<c8y-input-group-editable
|
|
196
|
+
disabled
|
|
197
|
+
ariaLabel="Disabled field"
|
|
198
|
+
[(ngModel)]="disabledModel"
|
|
199
|
+
></c8y-input-group-editable>
|
|
200
|
+
</div>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
`,
|
|
204
|
+
standalone: true,
|
|
205
|
+
imports: [CoreModule, InputGroupEditableComponent]
|
|
206
|
+
})
|
|
207
|
+
export class InputGroupEditableExampleComponent {
|
|
208
|
+
// Sizes
|
|
209
|
+
sizeDefault = 'Default size';
|
|
210
|
+
sizeSm = 'Small size';
|
|
211
|
+
sizeLg = 'Large size';
|
|
212
|
+
|
|
213
|
+
// Binding patterns
|
|
214
|
+
bindingTwoWay = 'Two-way binding';
|
|
215
|
+
bindingOneWay = 'One-way binding';
|
|
216
|
+
readonly bindingFormControl = new FormControl('Form control value', { nonNullable: true });
|
|
217
|
+
|
|
218
|
+
// Outputs
|
|
219
|
+
outputsModel = 'Edit and save or cancel';
|
|
220
|
+
lastAction = '';
|
|
221
|
+
lastSavedValue = '';
|
|
222
|
+
|
|
223
|
+
// Placeholder and name
|
|
224
|
+
placeholderModel = '';
|
|
225
|
+
|
|
226
|
+
// Sync validation — template-driven (required + minlength attributes)
|
|
227
|
+
validTplModel = 'min 3 chars required';
|
|
228
|
+
|
|
229
|
+
// Sync validation — [validators] input
|
|
230
|
+
validatorsModel = 'NoSpacesAllowed';
|
|
231
|
+
|
|
232
|
+
// Sync validation — [formControl] with custom <c8y-message> children
|
|
233
|
+
readonly validFormControl = new FormControl('min 3 chars required', {
|
|
234
|
+
validators: [Validators.required, Validators.minLength(3)],
|
|
235
|
+
nonNullable: true
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Disabled state
|
|
239
|
+
disabledModel = 'Read-only value';
|
|
240
|
+
|
|
241
|
+
// Async validation — simulates a server-side uniqueness check
|
|
242
|
+
readonly asyncFormControl = new FormControl('available', { nonNullable: true });
|
|
243
|
+
|
|
244
|
+
readonly noSpacesValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null =>
|
|
245
|
+
/\s/.test(control.value) ? { noSpaces: true } : null;
|
|
246
|
+
readonly uniquenessValidator: AsyncValidatorFn = (
|
|
247
|
+
control: AbstractControl
|
|
248
|
+
): Observable<ValidationErrors | null> => {
|
|
249
|
+
const takenNames = ['taken', 'admin', 'root'];
|
|
250
|
+
return timer(800).pipe(
|
|
251
|
+
map(() => (takenNames.includes(control.value?.toLowerCase()) ? { notUnique: true } : null))
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
onSave(value: string): void {
|
|
256
|
+
this.lastAction = 'Saved';
|
|
257
|
+
this.lastSavedValue = value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
onCancel(): void {
|
|
261
|
+
this.lastAction = 'Cancelled';
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { NgModule } from '@angular/core';
|
|
2
|
+
import { CommonModule } from '@angular/common';
|
|
3
|
+
import { NavigatorNode, hookNavigator, hookRoute } from '@c8y/ngx-components';
|
|
4
|
+
|
|
5
|
+
@NgModule({
|
|
6
|
+
imports: [CommonModule],
|
|
7
|
+
providers: [
|
|
8
|
+
hookRoute({
|
|
9
|
+
path: 'input-group-editable',
|
|
10
|
+
loadComponent: () =>
|
|
11
|
+
import('./input-group-editable-example.component').then(
|
|
12
|
+
m => m.InputGroupEditableExampleComponent
|
|
13
|
+
)
|
|
14
|
+
}),
|
|
15
|
+
hookNavigator(
|
|
16
|
+
new NavigatorNode({
|
|
17
|
+
path: '/input-group-editable',
|
|
18
|
+
label: 'Inline editable input',
|
|
19
|
+
icon: 'hand-o-right',
|
|
20
|
+
priority: 5
|
|
21
|
+
})
|
|
22
|
+
)
|
|
23
|
+
]
|
|
24
|
+
})
|
|
25
|
+
export class InputGroupEditableExampleModule {}
|
package/src/lazy-widget/index.ts
CHANGED
|
@@ -19,7 +19,10 @@ export function provideLazyWidget() {
|
|
|
19
19
|
loadComponent: loadViewComponent,
|
|
20
20
|
loadConfigComponent: loadConfigComponent,
|
|
21
21
|
data: {
|
|
22
|
-
schema: () =>
|
|
22
|
+
schema: () =>
|
|
23
|
+
import(
|
|
24
|
+
'c8y-schema-loader?interfaceName=WidgetConfig&type=widget-config!./widget-config.model'
|
|
25
|
+
)
|
|
23
26
|
}
|
|
24
27
|
})
|
|
25
28
|
];
|
package/src/widget/index.ts
CHANGED
|
@@ -12,7 +12,11 @@ export function provideDemoWidget() {
|
|
|
12
12
|
configComponent: WidgetConfigDemo,
|
|
13
13
|
errorStrategy: DynamicComponentErrorStrategy.OVERLAY_ERROR,
|
|
14
14
|
data: {
|
|
15
|
-
schema: () =>
|
|
15
|
+
schema: () =>
|
|
16
|
+
import(
|
|
17
|
+
'c8y-schema-loader?interfaceName=WidgetConfig&type=widget-config!./widget-config.model'
|
|
18
|
+
),
|
|
19
|
+
// The settings object can be used to configure the configComponent
|
|
16
20
|
settings: {
|
|
17
21
|
noNewWidgets: false
|
|
18
22
|
}
|
|
@@ -27,7 +27,10 @@ export function provideWidgetsResolverSample() {
|
|
|
27
27
|
},
|
|
28
28
|
errorStrategy: DynamicComponentErrorStrategy.OVERLAY_ERROR,
|
|
29
29
|
data: {
|
|
30
|
-
schema: () =>
|
|
30
|
+
schema: () =>
|
|
31
|
+
import(
|
|
32
|
+
'c8y-schema-loader?interfaceName=WidgetConfig&type=widget-config!./widget-config.model'
|
|
33
|
+
),
|
|
31
34
|
settings: {
|
|
32
35
|
noNewWidgets: false,
|
|
33
36
|
widgetDefaults: {
|