@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.
Files changed (41) hide show
  1. package/addon/components/cf-content.hbs +36 -39
  2. package/addon/components/cf-content.js +47 -20
  3. package/addon/components/cf-field/input/action-button.hbs +1 -1
  4. package/addon/components/cf-field/input/action-button.js +9 -7
  5. package/addon/components/cf-field/input/checkbox.hbs +2 -2
  6. package/addon/components/cf-field/input/checkbox.js +9 -29
  7. package/addon/components/cf-field/input/file.js +8 -9
  8. package/addon/components/cf-field/input/float.hbs +4 -4
  9. package/addon/components/cf-field/input/integer.hbs +5 -5
  10. package/addon/components/cf-field/input/table.js +12 -10
  11. package/addon/components/cf-field/input/text.hbs +5 -5
  12. package/addon/components/cf-field/input/textarea.hbs +5 -5
  13. package/addon/components/cf-field/input.js +1 -1
  14. package/addon/components/cf-field/label.hbs +1 -1
  15. package/addon/components/cf-field-value.js +8 -13
  16. package/addon/components/cf-field.hbs +2 -2
  17. package/addon/components/cf-field.js +2 -3
  18. package/addon/components/cf-navigation-item.hbs +2 -2
  19. package/addon/components/document-validity.js +1 -1
  20. package/addon/gql/fragments/field.graphql +27 -0
  21. package/addon/gql/mutations/save-document-table-answer.graphql +1 -1
  22. package/addon/gql/mutations/save-document.graphql +1 -0
  23. package/addon/gql/queries/{get-document-answers.graphql → document-answers.graphql} +2 -1
  24. package/addon/gql/queries/{get-document-forms.graphql → document-forms.graphql} +2 -1
  25. package/addon/gql/queries/{get-document-used-dynamic-options.graphql → document-used-dynamic-options.graphql} +2 -1
  26. package/addon/gql/queries/{get-dynamic-options.graphql → dynamic-options.graphql} +2 -1
  27. package/addon/gql/queries/{get-fileanswer-info.graphql → fileanswer-info.graphql} +2 -1
  28. package/addon/helpers/get-widget.js +50 -0
  29. package/addon/lib/answer.js +108 -72
  30. package/addon/lib/base.js +32 -23
  31. package/addon/lib/dependencies.js +36 -71
  32. package/addon/lib/document.js +92 -96
  33. package/addon/lib/field.js +334 -401
  34. package/addon/lib/fieldset.js +46 -47
  35. package/addon/lib/form.js +27 -15
  36. package/addon/lib/navigation.js +187 -181
  37. package/addon/lib/question.js +103 -94
  38. package/addon/services/caluma-store.js +10 -6
  39. package/app/helpers/get-widget.js +4 -0
  40. package/package.json +19 -18
  41. package/CHANGELOG.md +0 -21
@@ -1,9 +1,9 @@
1
1
  import { getOwner } from "@ember/application";
2
2
  import { assert } from "@ember/debug";
3
- import { computed, observer, defineProperty } from "@ember/object";
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 const NavigationItem = Base.extend({
23
- router: service(),
22
+ export class NavigationItem extends Base {
23
+ @service router;
24
24
 
25
- init(...args) {
26
- assert("A fieldset `fieldset` must be passed", this.fieldset);
27
- assert("A navigation `navigation` must be passed", this.navigation);
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
- defineProperty(this, "pk", {
30
- writable: false,
31
- value: `NavigationItem:${this.fieldset.pk}`,
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
- this._super(...args);
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
- parent: computed("_parentSlug", "navigation.items.@each.slug", function () {
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
- children: computed("slug", "navigation.items.@each._parentSlug", function () {
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
- visibleChildren: computed("children.@each.visible", function () {
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
- label: computed.or("fieldset.field.question.label", "fieldset.form.name"),
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
- slug: computed.or(
91
- "fieldset.field.question.subForm.slug",
92
- "fieldset.form.slug"
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
- _parentSlug: reads("fieldset.field.fieldset.field.question.subForm.slug"),
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
- active: computed(
111
- "router.currentRoute.queryParams.displayedForm",
112
- "slug",
113
- function () {
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
- childrenActive: computed("children.@each.active", function () {
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
- navigable: computed(
136
- "fieldset.field.hidden",
137
- "fieldset.fields.@each.{hidden,questionType}",
138
- function () {
139
- return (
140
- (this.fieldset.field === undefined || !this.fieldset.field.hidden) &&
141
- this.fieldset.fields.some(
142
- (field) => field.questionType !== "FormQuestion" && !field.hidden
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
- visible: computed("navigable", "visibleChildren.length", function () {
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
- state: computed(
171
- "fieldsetState",
172
- "visibleChildren.@each.fieldsetState",
173
- function () {
174
- const states = [
175
- this.fieldsetState,
176
- ...this.visibleChildren.map((child) => child.fieldsetState),
177
- ].filter(Boolean);
178
-
179
- if (states.every((state) => state === STATES.EMPTY)) {
180
- return STATES.EMPTY;
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
- dirty: computed(
201
- "fieldsetDirty",
202
- "visibleChildren.@each.fieldsetDirty",
203
- function () {
204
- return [
205
- this.fieldsetDirty,
206
- ...this.visibleChildren.map((child) => child.fieldsetDirty),
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
- visibleFields: computed(
219
- "fieldset.fields.@each.{questionType,hidden}",
220
- function () {
221
- return this.fieldset.fields.filter(
222
- (f) => f.questionType !== "FormQuestion" && !f.hidden
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
- fieldsetState: computed(
241
- "visibleFields.@each.{isNew,isValid,optional}",
242
- function () {
243
- if (!this.visibleFields.length) {
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
- fieldsetDirty: computed("visibleFields.@each.isDirty", function () {
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 const Navigation = Base.extend({
285
- router: service(),
286
- calumaStore: service(),
289
+ export class Navigation extends Base {
290
+ @service router;
287
291
 
288
- init(...args) {
289
- assert("A document `document` must be passed", this.document);
292
+ constructor({ document, ...args }) {
293
+ assert("`document` must be passed as an argument", document);
290
294
 
291
- defineProperty(this, "pk", {
292
- writable: false,
293
- value: `Navigation:${this.document.pk}`,
294
- });
295
+ super({ ...args });
295
296
 
296
- this._super(...args);
297
+ this.document = document;
297
298
 
298
- this.set("items", []);
299
+ this.pushIntoStore();
299
300
 
300
301
  this._createItems();
301
- },
302
302
 
303
- willDestroy(...args) {
304
- this._super(...args);
303
+ this.router.on("routeDidChange", this, "goToNextItemIfNonNavigable");
304
+ }
305
305
 
306
- const items = this.items;
307
- this.set("items", []);
308
- items.forEach((item) => item.destroy());
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
- getOwner(this)
318
- .factoryFor("caluma-model:navigation-item")
319
- .create({ fieldset, navigation: this })
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.set("items", items);
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: null,
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: null,
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
- rootItems: computed("items.@each.{parent,visible}", function () {
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: computed("items.@each.active", function () {
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: computed("currentItem", "items.@each.navigable", function () {
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: computed("currentItem", "items.@each.navigable", function () {
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 preventNonNavigableItem
405
+ * @method goToNextItemIfNonNavigable
400
406
  */
401
- preventNonNavigableItem: observer("currentItem", function () {
402
- if (!this.get("nextItem.slug") || this.get("currentItem.navigable")) {
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.get("nextItem.slug") || this.get("currentItem.navigable")) {
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;