@projectcaluma/ember-form 10.0.0 → 11.0.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
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;