@projectcaluma/ember-form 10.0.0 → 11.0.0-beta.1
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/addon/components/cf-content.hbs +36 -39
- package/addon/components/cf-content.js +47 -20
- package/addon/components/cf-field/input/action-button.hbs +1 -1
- package/addon/components/cf-field/input/action-button.js +9 -7
- package/addon/components/cf-field/input/checkbox.hbs +2 -2
- package/addon/components/cf-field/input/checkbox.js +9 -29
- package/addon/components/cf-field/input/file.js +8 -9
- package/addon/components/cf-field/input/float.hbs +4 -4
- package/addon/components/cf-field/input/integer.hbs +5 -5
- package/addon/components/cf-field/input/table.js +12 -10
- package/addon/components/cf-field/input/text.hbs +5 -5
- package/addon/components/cf-field/input/textarea.hbs +5 -5
- package/addon/components/cf-field/input.js +1 -1
- package/addon/components/cf-field/label.hbs +1 -1
- package/addon/components/cf-field-value.js +8 -13
- package/addon/components/cf-field.hbs +2 -2
- package/addon/components/cf-field.js +2 -3
- package/addon/components/cf-navigation-item.hbs +2 -2
- package/addon/components/document-validity.js +1 -1
- package/addon/gql/fragments/field.graphql +27 -0
- package/addon/gql/mutations/save-document-table-answer.graphql +1 -1
- package/addon/gql/mutations/save-document.graphql +1 -0
- package/addon/gql/queries/{get-document-answers.graphql → document-answers.graphql} +2 -1
- package/addon/gql/queries/{get-document-forms.graphql → document-forms.graphql} +2 -1
- package/addon/gql/queries/{get-document-used-dynamic-options.graphql → document-used-dynamic-options.graphql} +2 -1
- package/addon/gql/queries/{get-dynamic-options.graphql → dynamic-options.graphql} +2 -1
- package/addon/gql/queries/{get-fileanswer-info.graphql → fileanswer-info.graphql} +2 -1
- package/addon/helpers/get-widget.js +50 -0
- package/addon/lib/answer.js +108 -72
- package/addon/lib/base.js +32 -23
- package/addon/lib/dependencies.js +36 -71
- package/addon/lib/document.js +92 -96
- package/addon/lib/field.js +334 -401
- package/addon/lib/fieldset.js +46 -47
- package/addon/lib/form.js +27 -15
- package/addon/lib/navigation.js +187 -181
- package/addon/lib/question.js +103 -94
- package/addon/services/caluma-store.js +10 -6
- package/app/helpers/get-widget.js +4 -0
- package/package.json +19 -18
- package/CHANGELOG.md +0 -21
package/addon/lib/navigation.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
import { getOwner } from "@ember/application";
|
2
2
|
import { assert } from "@ember/debug";
|
3
|
-
import {
|
4
|
-
import { reads } from "@ember/object/computed";
|
3
|
+
import { associateDestroyableChild } from "@ember/destroyable";
|
5
4
|
import { later, once } from "@ember/runloop";
|
6
5
|
import { inject as service } from "@ember/service";
|
6
|
+
import { cached } from "tracked-toolbox";
|
7
7
|
|
8
8
|
import Base from "@projectcaluma/ember-form/lib/base";
|
9
9
|
|
@@ -19,87 +19,112 @@ const STATES = {
|
|
19
19
|
*
|
20
20
|
* @class NavigationItem
|
21
21
|
*/
|
22
|
-
export
|
23
|
-
router
|
22
|
+
export class NavigationItem extends Base {
|
23
|
+
@service router;
|
24
24
|
|
25
|
-
|
26
|
-
assert("
|
27
|
-
assert("
|
25
|
+
constructor({ fieldset, navigation, ...args }) {
|
26
|
+
assert("`fieldset` must be passed as an argument", fieldset);
|
27
|
+
assert("`navigation` must be passed as an argument", navigation);
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
super({ ...args });
|
30
|
+
|
31
|
+
this.fieldset = fieldset;
|
32
|
+
this.navigation = navigation;
|
33
|
+
|
34
|
+
this.pushIntoStore();
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* The fieldset to build the navigation item for
|
39
|
+
*
|
40
|
+
* @property {Fieldset} fieldset
|
41
|
+
*/
|
42
|
+
fieldset = null;
|
43
|
+
|
44
|
+
/**
|
45
|
+
* The navigation object this item originates from. This is used to determine
|
46
|
+
* the items child and parent items.
|
47
|
+
*
|
48
|
+
* @property {Navigation} navigation
|
49
|
+
*/
|
50
|
+
navigation = null;
|
33
51
|
|
34
|
-
|
35
|
-
|
52
|
+
/**
|
53
|
+
* The primary key of the navigation item.
|
54
|
+
*
|
55
|
+
* @property {String} pk
|
56
|
+
*/
|
57
|
+
@cached
|
58
|
+
get pk() {
|
59
|
+
return `NavigationItem:${this.fieldset.pk}`;
|
60
|
+
}
|
36
61
|
|
37
62
|
/**
|
38
63
|
* The parent navigation item
|
39
64
|
*
|
40
65
|
* @property {NavigationItem} parent
|
41
|
-
* @accessor
|
42
66
|
*/
|
43
|
-
|
67
|
+
@cached
|
68
|
+
get parent() {
|
44
69
|
return this.navigation.items.find((item) => item.slug === this._parentSlug);
|
45
|
-
}
|
70
|
+
}
|
46
71
|
|
47
72
|
/**
|
48
73
|
* The children of this navigation item
|
49
74
|
*
|
50
75
|
* @property {NavigationItem[]} children
|
51
|
-
* @accessor
|
52
76
|
*/
|
53
|
-
|
77
|
+
@cached
|
78
|
+
get children() {
|
54
79
|
return this.navigation.items.filter(
|
55
80
|
(item) => item._parentSlug === this.slug
|
56
81
|
);
|
57
|
-
}
|
82
|
+
}
|
58
83
|
|
59
84
|
/**
|
60
85
|
* The visible children of this navigation item
|
61
86
|
*
|
62
87
|
* @property {NavigationItem[]} visibleChildren
|
63
|
-
* @accessor
|
64
88
|
*/
|
65
|
-
|
89
|
+
@cached
|
90
|
+
get visibleChildren() {
|
66
91
|
return this.children.filter((child) => child.visible);
|
67
|
-
}
|
68
|
-
|
69
|
-
/**
|
70
|
-
* The fieldset to build the navigation item for
|
71
|
-
*
|
72
|
-
* @property {Fieldset} fieldset
|
73
|
-
*/
|
74
|
-
fieldset: null,
|
92
|
+
}
|
75
93
|
|
76
94
|
/**
|
77
95
|
* The label displayed in the navigation
|
78
96
|
*
|
79
97
|
* @property {String} label
|
80
|
-
* @accessor
|
81
98
|
*/
|
82
|
-
|
99
|
+
@cached
|
100
|
+
get label() {
|
101
|
+
return (
|
102
|
+
this.fieldset.field?.question.raw.label ?? this.fieldset.form.raw.name
|
103
|
+
);
|
104
|
+
}
|
83
105
|
|
84
106
|
/**
|
85
107
|
* The slug of the items form
|
86
108
|
*
|
87
109
|
* @property {String} slug
|
88
|
-
* @accessor
|
89
110
|
*/
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
111
|
+
@cached
|
112
|
+
get slug() {
|
113
|
+
return (
|
114
|
+
this.fieldset.field?.question.raw.subForm.slug ?? this.fieldset.form.slug
|
115
|
+
);
|
116
|
+
}
|
94
117
|
|
95
118
|
/**
|
96
119
|
* The slug of the parent items form
|
97
120
|
*
|
98
121
|
* @property {String} _parentSlug
|
99
|
-
* @accessor
|
100
122
|
* @private
|
101
123
|
*/
|
102
|
-
|
124
|
+
@cached
|
125
|
+
get _parentSlug() {
|
126
|
+
return this.fieldset.field?.fieldset.field?.question.raw.subForm.slug;
|
127
|
+
}
|
103
128
|
|
104
129
|
/**
|
105
130
|
* The item is active if the query param `displayedForm` is equal to the
|
@@ -107,24 +132,20 @@ export const NavigationItem = Base.extend({
|
|
107
132
|
*
|
108
133
|
* @property {Boolean} active
|
109
134
|
*/
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
return (
|
115
|
-
this.slug === this.get("router.currentRoute.queryParams.displayedForm")
|
116
|
-
);
|
117
|
-
}
|
118
|
-
),
|
135
|
+
@cached
|
136
|
+
get active() {
|
137
|
+
return this.slug === this.router.currentRoute?.queryParams.displayedForm;
|
138
|
+
}
|
119
139
|
|
120
140
|
/**
|
121
141
|
* Whether the item has active children
|
122
142
|
*
|
123
143
|
* @property {Boolean} childrenActive
|
124
144
|
*/
|
125
|
-
|
145
|
+
@cached
|
146
|
+
get childrenActive() {
|
126
147
|
return this.children.some((child) => child.active);
|
127
|
-
}
|
148
|
+
}
|
128
149
|
|
129
150
|
/**
|
130
151
|
* The item is navigable if it is not hidden and its fieldset contains at
|
@@ -132,27 +153,25 @@ export const NavigationItem = Base.extend({
|
|
132
153
|
*
|
133
154
|
* @property {Boolean} navigable
|
134
155
|
*/
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
(
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
);
|
145
|
-
}
|
146
|
-
),
|
156
|
+
@cached
|
157
|
+
get navigable() {
|
158
|
+
return (
|
159
|
+
(this.fieldset.field === undefined || !this.fieldset.field.hidden) &&
|
160
|
+
this.fieldset.fields.some(
|
161
|
+
(field) => field.questionType !== "FormQuestion" && !field.hidden
|
162
|
+
)
|
163
|
+
);
|
164
|
+
}
|
147
165
|
|
148
166
|
/**
|
149
167
|
* The item is visible if it is navigable or has at least one child item.
|
150
168
|
*
|
151
169
|
* @property {Boolean} visible
|
152
170
|
*/
|
153
|
-
|
171
|
+
@cached
|
172
|
+
get visible() {
|
154
173
|
return this.navigable || Boolean(this.visibleChildren.length);
|
155
|
-
}
|
174
|
+
}
|
156
175
|
|
157
176
|
/**
|
158
177
|
* The current state consisting of the items and the childrens fieldset
|
@@ -165,64 +184,53 @@ export const NavigationItem = Base.extend({
|
|
165
184
|
* - `valid` if every fieldset is `valid`
|
166
185
|
*
|
167
186
|
* @property {String} state
|
168
|
-
* @accessor
|
169
187
|
*/
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
if (states.some((state) => state === STATES.INVALID)) {
|
184
|
-
return STATES.INVALID;
|
185
|
-
}
|
186
|
-
|
187
|
-
return states.every((state) => state === STATES.VALID)
|
188
|
-
? STATES.VALID
|
189
|
-
: STATES.IN_PROGRESS;
|
188
|
+
@cached
|
189
|
+
get state() {
|
190
|
+
const states = [
|
191
|
+
this.fieldsetState,
|
192
|
+
...this.visibleChildren.map((child) => child.fieldsetState),
|
193
|
+
].filter(Boolean);
|
194
|
+
|
195
|
+
if (states.every((state) => state === STATES.EMPTY)) {
|
196
|
+
return STATES.EMPTY;
|
197
|
+
}
|
198
|
+
|
199
|
+
if (states.some((state) => state === STATES.INVALID)) {
|
200
|
+
return STATES.INVALID;
|
190
201
|
}
|
191
|
-
|
202
|
+
|
203
|
+
return states.every((state) => state === STATES.VALID)
|
204
|
+
? STATES.VALID
|
205
|
+
: STATES.IN_PROGRESS;
|
206
|
+
}
|
192
207
|
|
193
208
|
/**
|
194
209
|
* The dirty state of the navigation item. This will be true if at least one
|
195
210
|
* of the children or the navigation fieldset itself is dirty.
|
196
211
|
*
|
197
212
|
* @property {Boolean} fieldsetDirty
|
198
|
-
* @accessor
|
199
213
|
*/
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
].some(Boolean);
|
208
|
-
}
|
209
|
-
),
|
214
|
+
@cached
|
215
|
+
get dirty() {
|
216
|
+
return [
|
217
|
+
this.fieldsetDirty,
|
218
|
+
...this.visibleChildren.map((child) => child.fieldsetDirty),
|
219
|
+
].some(Boolean);
|
220
|
+
}
|
210
221
|
|
211
222
|
/**
|
212
223
|
* All visible fields (excluding form question fields) of the fieldset that
|
213
224
|
* are visible.
|
214
225
|
*
|
215
226
|
* @property {Field[]} visibleFields
|
216
|
-
* @accessor
|
217
227
|
*/
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
}
|
225
|
-
),
|
228
|
+
@cached
|
229
|
+
get visibleFields() {
|
230
|
+
return this.fieldset.fields.filter(
|
231
|
+
(f) => f.questionType !== "FormQuestion" && !f.hidden
|
232
|
+
);
|
233
|
+
}
|
226
234
|
|
227
235
|
/**
|
228
236
|
* The current state of the item's fieldset. This does not consider the state
|
@@ -235,137 +243,136 @@ export const NavigationItem = Base.extend({
|
|
235
243
|
* - `valid` if every field is valid
|
236
244
|
*
|
237
245
|
* @property {String} fieldsetState
|
238
|
-
* @accessor
|
239
246
|
*/
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
return null;
|
245
|
-
}
|
246
|
-
|
247
|
-
if (this.visibleFields.some((f) => !f.isValid && f.isDirty)) {
|
248
|
-
return STATES.INVALID;
|
249
|
-
}
|
250
|
-
|
251
|
-
if (this.visibleFields.every((f) => f.isNew)) {
|
252
|
-
return STATES.EMPTY;
|
253
|
-
}
|
254
|
-
|
255
|
-
if (
|
256
|
-
this.visibleFields
|
257
|
-
.filter((f) => !f.optional)
|
258
|
-
.every((f) => f.isValid && !f.isNew)
|
259
|
-
) {
|
260
|
-
return STATES.VALID;
|
261
|
-
}
|
262
|
-
|
263
|
-
return STATES.IN_PROGRESS;
|
247
|
+
@cached
|
248
|
+
get fieldsetState() {
|
249
|
+
if (!this.visibleFields.length) {
|
250
|
+
return null;
|
264
251
|
}
|
265
|
-
|
252
|
+
|
253
|
+
if (this.visibleFields.some((f) => !f.isValid && f.isDirty)) {
|
254
|
+
return STATES.INVALID;
|
255
|
+
}
|
256
|
+
|
257
|
+
if (this.visibleFields.every((f) => f.isNew)) {
|
258
|
+
return STATES.EMPTY;
|
259
|
+
}
|
260
|
+
|
261
|
+
if (
|
262
|
+
this.visibleFields
|
263
|
+
.filter((f) => !f.optional)
|
264
|
+
.every((f) => f.isValid && !f.isNew)
|
265
|
+
) {
|
266
|
+
return STATES.VALID;
|
267
|
+
}
|
268
|
+
|
269
|
+
return STATES.IN_PROGRESS;
|
270
|
+
}
|
266
271
|
|
267
272
|
/**
|
268
273
|
* The dirty state of the current fieldset. This will be true if at least one
|
269
274
|
* field in the fieldset is dirty.
|
270
275
|
*
|
271
276
|
* @property {Boolean} fieldsetDirty
|
272
|
-
* @accessor
|
273
277
|
*/
|
274
|
-
|
278
|
+
@cached
|
279
|
+
get fieldsetDirty() {
|
275
280
|
return this.visibleFields.some((f) => f.isDirty);
|
276
|
-
}
|
277
|
-
}
|
281
|
+
}
|
282
|
+
}
|
278
283
|
|
279
284
|
/**
|
280
285
|
* Object to represent a navigation state for a certain document.
|
281
286
|
*
|
282
287
|
* @class Navigation
|
283
288
|
*/
|
284
|
-
export
|
285
|
-
router
|
286
|
-
calumaStore: service(),
|
289
|
+
export class Navigation extends Base {
|
290
|
+
@service router;
|
287
291
|
|
288
|
-
|
289
|
-
assert("
|
292
|
+
constructor({ document, ...args }) {
|
293
|
+
assert("`document` must be passed as an argument", document);
|
290
294
|
|
291
|
-
|
292
|
-
writable: false,
|
293
|
-
value: `Navigation:${this.document.pk}`,
|
294
|
-
});
|
295
|
+
super({ ...args });
|
295
296
|
|
296
|
-
this.
|
297
|
+
this.document = document;
|
297
298
|
|
298
|
-
this.
|
299
|
+
this.pushIntoStore();
|
299
300
|
|
300
301
|
this._createItems();
|
301
|
-
},
|
302
302
|
|
303
|
-
|
304
|
-
|
303
|
+
this.router.on("routeDidChange", this, "goToNextItemIfNonNavigable");
|
304
|
+
}
|
305
305
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
306
|
+
/**
|
307
|
+
* The primary key of the navigation
|
308
|
+
*
|
309
|
+
* @property {String} pk
|
310
|
+
*/
|
311
|
+
@cached
|
312
|
+
get pk() {
|
313
|
+
return `Navigation:${this.document.pk}`;
|
314
|
+
}
|
310
315
|
|
311
316
|
_createItems() {
|
317
|
+
const owner = getOwner(this);
|
318
|
+
|
312
319
|
const items = this.document.fieldsets.map((fieldset) => {
|
313
320
|
const pk = `NavigationItem:${fieldset.pk}`;
|
314
321
|
|
315
|
-
return (
|
322
|
+
return associateDestroyableChild(
|
323
|
+
this,
|
316
324
|
this.calumaStore.find(pk) ||
|
317
|
-
|
318
|
-
|
319
|
-
|
325
|
+
new (owner.factoryFor("caluma-model:navigation-item").class)({
|
326
|
+
fieldset,
|
327
|
+
navigation: this,
|
328
|
+
owner,
|
329
|
+
})
|
320
330
|
);
|
321
331
|
});
|
322
332
|
|
323
|
-
this.
|
324
|
-
}
|
333
|
+
this.items = items;
|
334
|
+
}
|
325
335
|
|
326
336
|
/**
|
327
337
|
* The document to build the navigation for
|
328
338
|
*
|
329
339
|
* @property {Document} document
|
330
340
|
*/
|
331
|
-
document
|
341
|
+
document = null;
|
332
342
|
|
333
343
|
/**
|
334
344
|
* The navigation items for the given document
|
335
345
|
*
|
336
346
|
* @property {NavigationItem[]} items
|
337
|
-
* @accessor
|
338
347
|
*/
|
339
|
-
items
|
348
|
+
items = [];
|
340
349
|
|
341
350
|
/**
|
342
351
|
* The top level navigation items. Those are items without a parent item that
|
343
352
|
* are visible.
|
344
353
|
*
|
345
354
|
* @property {NavigationItem[]} rootItems
|
346
|
-
* @accessor
|
347
355
|
*/
|
348
|
-
|
356
|
+
@cached
|
357
|
+
get rootItems() {
|
349
358
|
return this.items.filter((i) => !i.parent && i.visible);
|
350
|
-
}
|
359
|
+
}
|
351
360
|
|
352
361
|
/**
|
353
362
|
* The currently active navigation item
|
354
363
|
*
|
355
364
|
* @property {NavigationItem} currentItem
|
356
|
-
* @accessor
|
357
365
|
*/
|
358
|
-
currentItem
|
366
|
+
get currentItem() {
|
359
367
|
return this.items.find((item) => item.active);
|
360
|
-
}
|
368
|
+
}
|
361
369
|
|
362
370
|
/**
|
363
371
|
* The next navigable item in the navigation tree
|
364
372
|
*
|
365
373
|
* @property {NavigationItem} nextItem
|
366
|
-
* @accessor
|
367
374
|
*/
|
368
|
-
nextItem
|
375
|
+
get nextItem() {
|
369
376
|
if (!this.currentItem)
|
370
377
|
return this.items.filter((item) => item.navigable)[0];
|
371
378
|
|
@@ -374,15 +381,14 @@ export const Navigation = Base.extend({
|
|
374
381
|
.filter((item) => item.navigable);
|
375
382
|
|
376
383
|
return items.length ? items[0] : null;
|
377
|
-
}
|
384
|
+
}
|
378
385
|
|
379
386
|
/**
|
380
387
|
* The previous navigable item in the navigation tree
|
381
388
|
*
|
382
389
|
* @property {NavigationItem} previousItem
|
383
|
-
* @accessor
|
384
390
|
*/
|
385
|
-
previousItem
|
391
|
+
get previousItem() {
|
386
392
|
if (!this.currentItem) return null;
|
387
393
|
|
388
394
|
const items = this.items
|
@@ -390,21 +396,21 @@ export const Navigation = Base.extend({
|
|
390
396
|
.filter((item) => item.navigable);
|
391
397
|
|
392
398
|
return items.length ? items[items.length - 1] : null;
|
393
|
-
}
|
399
|
+
}
|
394
400
|
|
395
401
|
/**
|
396
402
|
* Observer which transitions to the next navigable item if the current item
|
397
403
|
* is not navigable.
|
398
404
|
*
|
399
|
-
* @method
|
405
|
+
* @method goToNextItemIfNonNavigable
|
400
406
|
*/
|
401
|
-
|
402
|
-
if (!this.
|
407
|
+
goToNextItemIfNonNavigable() {
|
408
|
+
if (!this.nextItem?.slug || this.currentItem?.navigable) {
|
403
409
|
return;
|
404
410
|
}
|
405
411
|
|
406
412
|
later(this, () => once(this, "goToNextItem"));
|
407
|
-
}
|
413
|
+
}
|
408
414
|
|
409
415
|
/**
|
410
416
|
* Replace the current item with the next navigable item
|
@@ -412,14 +418,14 @@ export const Navigation = Base.extend({
|
|
412
418
|
* @method goToNextItem
|
413
419
|
*/
|
414
420
|
goToNextItem() {
|
415
|
-
if (!this.
|
421
|
+
if (!this.nextItem?.slug || this.currentItem?.navigable) {
|
416
422
|
return;
|
417
423
|
}
|
418
424
|
|
419
425
|
this.router.replaceWith({
|
420
426
|
queryParams: { displayedForm: this.nextItem.slug },
|
421
427
|
});
|
422
|
-
}
|
423
|
-
}
|
428
|
+
}
|
429
|
+
}
|
424
430
|
|
425
431
|
export default Navigation;
|