@projectcaluma/ember-form 10.0.3 → 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 +45 -18
- 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 +1 -1
- package/addon/components/cf-field/input/checkbox.js +9 -29
- package/addon/components/cf-field/input/file.js +7 -8
- package/addon/components/cf-field/input/float.hbs +2 -2
- package/addon/components/cf-field/input/integer.hbs +3 -3
- package/addon/components/cf-field/input/table.js +12 -10
- package/addon/components/cf-field/input/text.hbs +3 -3
- package/addon/components/cf-field/input/textarea.hbs +3 -3
- 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 +7 -12
- package/addon/components/cf-field.hbs +2 -2
- package/addon/components/cf-field.js +2 -3
- package/addon/components/document-validity.js +1 -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 +333 -400
- 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 +102 -93
- package/addon/services/caluma-store.js +10 -6
- package/app/helpers/get-widget.js +4 -0
- package/package.json +14 -13
package/addon/lib/answer.js
CHANGED
@@ -1,130 +1,166 @@
|
|
1
1
|
import { getOwner } from "@ember/application";
|
2
2
|
import { assert } from "@ember/debug";
|
3
|
-
import { computed } from "@ember/object";
|
4
|
-
import { inject as service } from "@ember/service";
|
5
3
|
import { camelize } from "@ember/string";
|
6
4
|
import { isEmpty } from "@ember/utils";
|
5
|
+
import { dedupeTracked, cached } from "tracked-toolbox";
|
7
6
|
|
8
7
|
import { decodeId } from "@projectcaluma/ember-core/helpers/decode-id";
|
9
8
|
import Base from "@projectcaluma/ember-form/lib/base";
|
10
9
|
import { parseDocument } from "@projectcaluma/ember-form/lib/parsers";
|
11
10
|
|
11
|
+
/**
|
12
|
+
* Class that automatically defines all keys of the passed object as deduped
|
13
|
+
* tracked property and assigns the initial value.
|
14
|
+
*
|
15
|
+
* @class DedupedTrackedObject
|
16
|
+
* @private
|
17
|
+
*/
|
18
|
+
class DedupedTrackedObject {
|
19
|
+
constructor(obj) {
|
20
|
+
Object.entries(obj).forEach(([key, value]) => {
|
21
|
+
Object.defineProperty(
|
22
|
+
this,
|
23
|
+
key,
|
24
|
+
dedupeTracked(this, key, { initializer: () => value })
|
25
|
+
);
|
26
|
+
});
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
12
30
|
/**
|
13
31
|
* Object which represents an answer in context of a field
|
14
32
|
*
|
15
33
|
* @class Answer
|
16
34
|
*/
|
17
|
-
export default Base
|
18
|
-
|
35
|
+
export default class Answer extends Base {
|
36
|
+
constructor({ raw, field, ...args }) {
|
37
|
+
assert("`field` must be passed as an argument", field);
|
19
38
|
|
20
|
-
init(...args) {
|
21
39
|
assert(
|
22
40
|
"A graphql answer `raw` must be passed",
|
23
|
-
|
41
|
+
/Answer$/.test(raw?.__typename)
|
24
42
|
);
|
25
43
|
|
26
|
-
|
27
|
-
|
28
|
-
|
44
|
+
super({ raw, ...args });
|
45
|
+
|
46
|
+
this.field = field;
|
47
|
+
this.raw = new DedupedTrackedObject(raw);
|
29
48
|
|
30
|
-
this.
|
49
|
+
this.pushIntoStore();
|
50
|
+
}
|
51
|
+
|
52
|
+
/**
|
53
|
+
* The field this answer originates from
|
54
|
+
*
|
55
|
+
* @property {Field} field
|
56
|
+
*/
|
57
|
+
field = null;
|
58
|
+
|
59
|
+
/**
|
60
|
+
* The raw data of the answer. This is the only `raw` property that is tracked
|
61
|
+
* since only the answers properties are changed while rendering the form.
|
62
|
+
*
|
63
|
+
* @property {DedupedTrackedObject} raw
|
64
|
+
*/
|
65
|
+
raw = {};
|
31
66
|
|
32
|
-
|
33
|
-
|
67
|
+
/**
|
68
|
+
* The primary key of the answer.
|
69
|
+
*
|
70
|
+
* @property {String} pk
|
71
|
+
*/
|
72
|
+
@cached
|
73
|
+
get pk() {
|
74
|
+
return this.uuid && `Answer:${this.uuid}`;
|
75
|
+
}
|
34
76
|
|
35
77
|
/**
|
36
78
|
* The uuid of the answer
|
37
79
|
*
|
38
80
|
* @property {String} uuid
|
39
|
-
* @accessor
|
40
81
|
*/
|
41
|
-
|
82
|
+
@cached
|
83
|
+
get uuid() {
|
42
84
|
return this.raw.id ? decodeId(this.raw.id) : null;
|
43
|
-
}
|
85
|
+
}
|
44
86
|
|
45
|
-
|
87
|
+
/**
|
88
|
+
* Whether the answer is new. This is true when there is no object from the
|
89
|
+
* backend or the value is empty.
|
90
|
+
*
|
91
|
+
* @property {Boolean} isNew
|
92
|
+
*/
|
93
|
+
@cached
|
94
|
+
get isNew() {
|
46
95
|
return !this.uuid || isEmpty(this.value);
|
47
|
-
}
|
96
|
+
}
|
48
97
|
|
49
98
|
/**
|
50
99
|
* The name of the property in which the value is stored. This depends on the
|
51
100
|
* type of the answer.
|
52
101
|
*
|
53
|
-
*
|
54
102
|
* @property {String} _valueKey
|
55
|
-
* @accessor
|
56
103
|
* @private
|
57
104
|
*/
|
58
|
-
|
105
|
+
@cached
|
106
|
+
get _valueKey() {
|
59
107
|
return (
|
60
|
-
this.__typename &&
|
108
|
+
this.raw.__typename &&
|
109
|
+
camelize(this.raw.__typename.replace(/Answer$/, "Value"))
|
61
110
|
);
|
62
|
-
}
|
111
|
+
}
|
63
112
|
|
64
113
|
/**
|
65
114
|
* The value of the answer, the type of this value depends on the type of the
|
66
115
|
* answer. For table answers this returns an array of documents.
|
67
116
|
*
|
68
117
|
* @property {String|Number|String[]|Document[]} value
|
69
|
-
* @computed
|
70
118
|
*/
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
"
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
return value;
|
104
|
-
},
|
105
|
-
set(_, value) {
|
106
|
-
value = [undefined, ""].includes(value) ? null : value;
|
107
|
-
|
108
|
-
if (this._valueKey) {
|
109
|
-
this.set(this._valueKey, value);
|
110
|
-
}
|
111
|
-
|
112
|
-
return value;
|
113
|
-
},
|
119
|
+
@cached
|
120
|
+
get value() {
|
121
|
+
const value = this.raw[this._valueKey];
|
122
|
+
|
123
|
+
if (this._valueKey === "tableValue" && value) {
|
124
|
+
const owner = getOwner(this);
|
125
|
+
const Document = owner.factoryFor("caluma-model:document").class;
|
126
|
+
|
127
|
+
return value.map((document) => {
|
128
|
+
if (document instanceof Document) return document;
|
129
|
+
|
130
|
+
const existing = this.calumaStore.find(
|
131
|
+
`Document:${decodeId(document.id)}`
|
132
|
+
);
|
133
|
+
|
134
|
+
return (
|
135
|
+
existing ||
|
136
|
+
new Document({
|
137
|
+
raw: parseDocument(document),
|
138
|
+
parentDocument: this.field.document,
|
139
|
+
owner,
|
140
|
+
})
|
141
|
+
);
|
142
|
+
});
|
143
|
+
}
|
144
|
+
|
145
|
+
return value;
|
146
|
+
}
|
147
|
+
|
148
|
+
set value(value) {
|
149
|
+
if (this._valueKey) {
|
150
|
+
this.raw[this._valueKey] = [undefined, ""].includes(value) ? null : value;
|
114
151
|
}
|
115
|
-
|
152
|
+
}
|
116
153
|
|
117
154
|
/**
|
118
155
|
* The value serialized for a backend request.
|
119
156
|
*
|
120
157
|
* @property {String|Number|String[]} serializedValue
|
121
|
-
* @accessor
|
122
158
|
*/
|
123
|
-
serializedValue
|
124
|
-
if (this.__typename === "TableAnswer") {
|
159
|
+
get serializedValue() {
|
160
|
+
if (this.raw.__typename === "TableAnswer") {
|
125
161
|
return (this.value || []).map(({ uuid }) => uuid);
|
126
162
|
}
|
127
163
|
|
128
164
|
return this.value;
|
129
|
-
}
|
130
|
-
}
|
165
|
+
}
|
166
|
+
}
|
package/addon/lib/base.js
CHANGED
@@ -1,35 +1,44 @@
|
|
1
|
-
import {
|
1
|
+
import { setOwner } from "@ember/application";
|
2
2
|
import { assert } from "@ember/debug";
|
3
|
-
import
|
3
|
+
import { registerDestructor } from "@ember/destroyable";
|
4
4
|
import { inject as service } from "@ember/service";
|
5
5
|
|
6
|
-
export default
|
7
|
-
calumaStore
|
6
|
+
export default class Base {
|
7
|
+
@service calumaStore;
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
constructor({ raw, owner }) {
|
10
|
+
assert("`owner` must be passed as an argument", owner);
|
11
11
|
|
12
|
-
assert("
|
12
|
+
assert("A primary key `pk` must be defined on the object", "pk" in this);
|
13
13
|
|
14
|
-
|
15
|
-
if (!/Answer$/.test(this.get("raw.__typename"))) {
|
16
|
-
assert("A primary key `pk` must be passed", this.pk);
|
17
|
-
assert(
|
18
|
-
"The primary key `pk` must be readonly",
|
19
|
-
!Object.getOwnPropertyDescriptor(this, "pk").writable
|
20
|
-
);
|
21
|
-
}
|
14
|
+
setOwner(this, owner);
|
22
15
|
|
23
|
-
if (
|
24
|
-
this.
|
16
|
+
if (raw) {
|
17
|
+
this.raw = raw;
|
25
18
|
}
|
26
|
-
},
|
27
19
|
|
28
|
-
|
29
|
-
|
20
|
+
registerDestructor(this, () => {
|
21
|
+
if (this.pk) {
|
22
|
+
this.calumaStore.delete(this.pk);
|
23
|
+
}
|
24
|
+
});
|
25
|
+
}
|
30
26
|
|
27
|
+
/**
|
28
|
+
* The raw data of the object
|
29
|
+
*
|
30
|
+
* @property {Object} raw
|
31
|
+
*/
|
32
|
+
raw = {};
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Push the object into the caluma store
|
36
|
+
*
|
37
|
+
* @method pushIntoStore
|
38
|
+
*/
|
39
|
+
pushIntoStore() {
|
31
40
|
if (this.pk) {
|
32
|
-
this.calumaStore.
|
41
|
+
this.calumaStore.push(this);
|
33
42
|
}
|
34
|
-
}
|
35
|
-
}
|
43
|
+
}
|
44
|
+
}
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
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
|
-
*
|
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[]}
|
54
|
+
* @return {Field[]} An array of all dependency fields
|
63
55
|
*/
|
64
|
-
export function
|
65
|
-
return
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
) {
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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;
|