@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,4 +1,5 @@
1
- import { computed, get } from "@ember/object";
1
+ import { get } from "@ember/object";
2
+ import { cached } from "tracked-toolbox";
2
3
 
3
4
  import { getAST, getTransforms } from "@projectcaluma/ember-core/utils/jexl";
4
5
 
@@ -47,79 +48,43 @@ export function getDependenciesFromJexl(jexl, expression) {
47
48
  }
48
49
 
49
50
  /**
50
- * Computed property to get all nested dependency parents of an expression. A
51
- * nested dependency parent would be a table field that is used with a mapby
52
- * transform in the JEXL expression.
53
- *
54
- * E.g: 'foo'|answer in 'bar'|answer|mapby('column') where 'bar' would be a
55
- * nested dependency parent.
56
- *
57
- * Those need to be extracted seperately since the overall dependencies need to
58
- * depend on the values of the nested dependency parents to recompute
59
- * correctly.
51
+ * Getter to extract all fields used in an expression.
60
52
  *
61
53
  * @param {String} expressionPath The path of the expression
62
- * @return {Field[]} Returns an array of nested dependency parent fields
54
+ * @return {Field[]} An array of all dependency fields
63
55
  */
64
- export function nestedDependencyParents(expressionPath) {
65
- return dependencies(expressionPath, { onlyNestedParents: true });
66
- }
67
-
68
- /**
69
- * Computed property to get all dependencies of an expression.
70
- *
71
- * @param {String} expressionPath The path of the expression
72
- * @param {Object} options
73
- * @param {Boolean} options.onlyNestedParents Only include nested parent fields
74
- * @param {String} options.nestedParentsPath Path of the nested parent fields to trigger recomputation
75
- * @return {Field[]} Returns an array of all dependency fields
76
- */
77
- export function dependencies(
78
- expressionPath,
79
- { onlyNestedParents = false, nestedParentsPath = null } = {}
80
- ) {
81
- // If there are nested parents we need to recompute the property if their
82
- // values change
83
- const nestedTriggers = nestedParentsPath
84
- ? [`${nestedParentsPath}.@each.value`]
85
- : [];
86
-
87
- return computed(
88
- "document.{jexl,fields.[]}",
89
- expressionPath,
90
- ...nestedTriggers,
91
- function () {
92
- const expression = get(this, expressionPath);
93
-
94
- if (!expression) return [];
95
-
96
- const slugs = getDependenciesFromJexl(this.document.jexl, expression);
97
-
98
- return slugs
99
- .flatMap((slug) => {
100
- const [fieldSlug, nestedSlug = null] = slug.split(".");
101
-
102
- if (onlyNestedParents && !nestedSlug) {
103
- return null;
104
- }
105
-
106
- const field = this.document.findField(fieldSlug);
107
-
108
- if (!onlyNestedParents && nestedSlug && field?.value) {
109
- // Get the nested fields from the parents value (rows)
110
- const childFields =
111
- nestedSlug === "__all__"
112
- ? field.value.flatMap((row) => row.fields)
113
- : field.value.map((row) => row.findField(nestedSlug));
114
-
115
- return [field, ...childFields];
116
- }
117
-
118
- return [field];
119
- })
120
- .filter(Boolean);
121
- }
122
- );
56
+ export function dependencies(expressionPath) {
57
+ return function (target, key) {
58
+ return cached(target, key, {
59
+ get() {
60
+ const expression = get(this, expressionPath);
61
+
62
+ if (!expression) return [];
63
+
64
+ const slugs = getDependenciesFromJexl(this.document.jexl, expression);
65
+
66
+ return slugs
67
+ .flatMap((slug) => {
68
+ const [fieldSlug, nestedSlug = null] = slug.split(".");
69
+
70
+ const field = this.document.findField(fieldSlug);
71
+
72
+ if (nestedSlug && field?.value) {
73
+ // Get the nested fields from the parents value (rows)
74
+ const childFields =
75
+ nestedSlug === "__all__"
76
+ ? field.value.flatMap((row) => row.fields)
77
+ : field.value.map((row) => row.findField(nestedSlug));
78
+
79
+ return [field, ...childFields];
80
+ }
81
+
82
+ return [field];
83
+ })
84
+ .filter(Boolean);
85
+ },
86
+ });
87
+ };
123
88
  }
124
89
 
125
90
  export default dependencies;
@@ -1,8 +1,8 @@
1
1
  import { getOwner } from "@ember/application";
2
2
  import { assert } from "@ember/debug";
3
- import { computed, defineProperty } from "@ember/object";
4
- import { inject as service } from "@ember/service";
3
+ import { associateDestroyableChild } from "@ember/destroyable";
5
4
  import jexl from "jexl";
5
+ import { cached } from "tracked-toolbox";
6
6
 
7
7
  import { decodeId } from "@projectcaluma/ember-core/helpers/decode-id";
8
8
  import { intersects, mapby } from "@projectcaluma/ember-core/utils/jexl";
@@ -17,124 +17,123 @@ const sum = (nums) => nums.reduce((num, base) => base + num, 0);
17
17
  *
18
18
  * @class Document
19
19
  */
20
- export default Base.extend({
21
- calumaStore: service(),
22
-
23
- init(...args) {
20
+ export default class Document extends Base {
21
+ constructor({ raw, parentDocument, ...args }) {
24
22
  assert(
25
23
  "A graphql document `raw` must be passed",
26
- this.raw && this.raw.__typename === "Document"
24
+ raw?.__typename === "Document"
27
25
  );
28
26
 
29
- defineProperty(this, "pk", {
30
- writable: false,
31
- value: `Document:${decodeId(this.raw.id)}`,
32
- });
27
+ super({ raw, ...args });
33
28
 
34
- this._super(...args);
29
+ this.parentDocument = parentDocument;
35
30
 
36
- this.set("fieldsets", []);
31
+ this.pushIntoStore();
37
32
 
38
33
  this._createRootForm();
39
34
  this._createFieldsets();
40
- },
35
+ }
41
36
 
42
37
  _createRootForm() {
43
- const rootForm =
44
- this.calumaStore.find(`Form:${this.raw.rootForm.slug}`) ||
45
- getOwner(this)
46
- .factoryFor("caluma-model:form")
47
- .create({ raw: this.raw.rootForm });
38
+ const owner = getOwner(this);
48
39
 
49
- this.set("rootForm", rootForm);
50
- },
40
+ this.rootForm =
41
+ this.calumaStore.find(`Form:${this.raw.rootForm.slug}`) ||
42
+ new (owner.factoryFor("caluma-model:form").class)({
43
+ raw: this.raw.rootForm,
44
+ owner,
45
+ });
46
+ }
51
47
 
52
48
  _createFieldsets() {
53
- const fieldsets = this.raw.forms.map((form) => {
54
- return (
49
+ const owner = getOwner(this);
50
+
51
+ this.fieldsets = this.raw.forms.map((form) => {
52
+ return associateDestroyableChild(
53
+ this,
55
54
  this.calumaStore.find(`${this.pk}:Form:${form.slug}`) ||
56
- getOwner(this)
57
- .factoryFor("caluma-model:fieldset")
58
- .create({
55
+ new (owner.factoryFor("caluma-model:fieldset").class)({
59
56
  raw: { form, answers: this.raw.answers },
60
57
  document: this,
58
+ owner,
61
59
  })
62
60
  );
63
61
  });
64
-
65
- this.set("fieldsets", fieldsets);
66
- },
67
-
68
- willDestroy(...args) {
69
- this._super(...args);
70
-
71
- const fieldsets = this.fieldsets;
72
- this.set("fieldsets", []);
73
- fieldsets.forEach((fieldset) => fieldset.destroy());
74
- },
62
+ }
75
63
 
76
64
  /**
77
- * The uuid of the document
65
+ * The parent document of this document. If this is set, the document is most
66
+ * likely a table row.
78
67
  *
79
- * @property {String} uuid
80
- * @accessor
68
+ * @property {Document} parentDocument
81
69
  */
82
- uuid: computed("raw.id", function () {
83
- return decodeId(this.raw.id);
84
- }),
85
-
86
- workItemUuid: computed(
87
- "raw.{workItem.id,case.workItems.edges.[]}",
88
- function () {
89
- // The document is either directly attached to a work item (via
90
- // CompleteTaskFormTask) or it's the case document and therefore
91
- // indirectly attached to a work item (via CompleteWorkflowFormTask)
92
- const rawId =
93
- this.raw.workItem?.id ||
94
- this.raw.case?.workItems.edges.find(
95
- (edge) => edge.node.task.__typename === "CompleteWorkflowFormTask"
96
- )?.node.id;
97
-
98
- return rawId ? decodeId(rawId) : null;
99
- }
100
- ),
70
+ parentDocument = null;
101
71
 
102
72
  /**
103
73
  * The root form of this document
104
74
  *
105
75
  * @property {Form} rootForm
106
- * @accessor
107
76
  */
108
- rootForm: null,
77
+ rootForm = null;
109
78
 
110
79
  /**
111
80
  * The fieldsets of this document
112
81
  *
113
82
  * @property {Fieldset[]} fieldsets
114
- * @accessor
115
83
  */
116
- fieldsets: null,
84
+ fieldsets = [];
85
+
86
+ /**
87
+ * The primary key of the document.
88
+ *
89
+ * @property {String} pk
90
+ */
91
+ @cached
92
+ get pk() {
93
+ return `Document:${this.uuid}`;
94
+ }
95
+
96
+ /**
97
+ * The uuid of the document
98
+ *
99
+ * @property {String} uuid
100
+ */
101
+ @cached
102
+ get uuid() {
103
+ return decodeId(this.raw.id);
104
+ }
105
+
106
+ @cached
107
+ get workItemUuid() {
108
+ // The document is either directly attached to a work item (via
109
+ // CompleteTaskFormTask) or it's the case document and therefore
110
+ // indirectly attached to a work item (via CompleteWorkflowFormTask)
111
+ const rawId =
112
+ this.raw.workItem?.id ||
113
+ this.raw.case?.workItems.edges.find(
114
+ (edge) => edge.node.task.__typename === "CompleteWorkflowFormTask"
115
+ )?.node.id;
116
+
117
+ return rawId ? decodeId(rawId) : null;
118
+ }
117
119
 
118
120
  /**
119
121
  * All fields of all fieldsets of this document
120
122
  *
121
123
  * @property {Field[]} fields
122
- * @accessor
123
124
  */
124
- fields: computed("fieldsets.@each.fields", function () {
125
- return this.fieldsets.reduce(
126
- (fields, fieldset) => [...fields, ...fieldset.fields],
127
- []
128
- );
129
- }),
125
+ @cached
126
+ get fields() {
127
+ return this.fieldsets.flatMap((fieldset) => fieldset.fields);
128
+ }
130
129
 
131
130
  /**
132
131
  * The JEXL object for evaluating jexl expressions on this document
133
132
  *
134
133
  * @property {JEXL} jexl
135
- * @accessor
136
134
  */
137
- jexl: computed(function () {
135
+ @cached
136
+ get jexl() {
138
137
  const documentJexl = new jexl.Jexl();
139
138
 
140
139
  documentJexl.addTransform("answer", (slug, defaultValue) =>
@@ -174,31 +173,29 @@ export default Base.extend({
174
173
  documentJexl.addTransform("stringify", (input) => JSON.stringify(input));
175
174
 
176
175
  return documentJexl;
177
- }),
176
+ }
178
177
 
179
178
  /**
180
179
  * The JEXL context object for passing to the evaluation of jexl expessions
181
180
  *
182
181
  * @property {Object} jexlContext
183
- * @accessor
184
182
  */
185
- jexlContext: computed(
186
- "rootForm.{slug,meta}",
187
- "parentDocument.jexlContext",
188
- function () {
189
- if (this.parentDocument) return this.parentDocument.jexlContext;
190
-
191
- return {
183
+ get jexlContext() {
184
+ return (
185
+ this.parentDocument?.jexlContext ?? {
192
186
  // JEXL interprets null in an expression as variable instead of a
193
187
  // primitive. This resolves that issue.
194
188
  null: null,
195
189
  form: this.rootForm.slug,
196
190
  info: {
197
- root: { form: this.rootForm.slug, formMeta: this.rootForm.meta },
191
+ root: {
192
+ form: this.rootForm.slug,
193
+ formMeta: this.rootForm.raw.meta,
194
+ },
198
195
  },
199
- };
200
- }
201
- ),
196
+ }
197
+ );
198
+ }
202
199
 
203
200
  /**
204
201
  * Object representation of a document. The question slug as key and the
@@ -215,9 +212,9 @@ export default Base.extend({
215
212
  * answer.
216
213
  *
217
214
  * @property {Object} flatAnswerMap
218
- * @accessor
219
215
  */
220
- flatAnswerMap: computed("fields.@each.{question,value}", function () {
216
+ @cached
217
+ get flatAnswerMap() {
221
218
  return this.fields.reduce(
222
219
  (answerMap, field) => ({
223
220
  ...answerMap,
@@ -225,7 +222,7 @@ export default Base.extend({
225
222
  }),
226
223
  {}
227
224
  );
228
- }),
225
+ }
229
226
 
230
227
  /**
231
228
  * Find an answer for a given question slug
@@ -249,7 +246,7 @@ export default Base.extend({
249
246
  return defaultValue ?? field.question.isMultipleChoice ? [] : null;
250
247
  }
251
248
 
252
- if (field.question.__typename === "TableQuestion") {
249
+ if (field.question.isTable) {
253
250
  return field.value.map((doc) =>
254
251
  doc.fields
255
252
  .filter((field) => !field.hidden)
@@ -263,7 +260,7 @@ export default Base.extend({
263
260
  }
264
261
 
265
262
  return field.value;
266
- },
263
+ }
267
264
 
268
265
  /**
269
266
  * Find a field in the document by a given question slug
@@ -272,9 +269,8 @@ export default Base.extend({
272
269
  * @return {Field} The wanted field
273
270
  */
274
271
  findField(slug) {
275
- return [
276
- ...this.fields,
277
- ...(this.parentDocument ? this.parentDocument.fields : []),
278
- ].find((field) => field.question.slug === slug);
279
- },
280
- });
272
+ return [...this.fields, ...(this.parentDocument?.fields ?? [])].find(
273
+ (field) => field.question.slug === slug
274
+ );
275
+ }
276
+ }