@projectcaluma/ember-form 11.0.0-beta.4 → 11.0.0-beta.40
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/CHANGELOG.md +202 -0
- package/addon/components/cf-content.hbs +38 -37
- package/addon/components/cf-content.js +7 -3
- package/addon/components/cf-field/hint.hbs +5 -0
- package/addon/components/cf-field/input/action-button.hbs +8 -4
- package/addon/components/cf-field/input/action-button.js +60 -59
- package/addon/components/cf-field/input/checkbox.hbs +2 -3
- package/addon/components/cf-field/input/date.hbs +12 -25
- package/addon/components/cf-field/input/date.js +19 -11
- package/addon/components/cf-field/input/files.hbs +38 -0
- package/addon/components/cf-field/input/files.js +113 -0
- package/addon/components/cf-field/input/powerselect.hbs +27 -29
- package/addon/components/cf-field/input/powerselect.js +8 -2
- package/addon/components/cf-field/input/radio.hbs +2 -2
- package/addon/components/cf-field/input/static.hbs +1 -1
- package/addon/components/cf-field/input/table.hbs +20 -18
- package/addon/components/cf-field/input/table.js +1 -11
- package/addon/components/cf-field/input.hbs +8 -21
- package/addon/components/cf-field/input.js +32 -14
- package/addon/components/cf-field/label.hbs +4 -2
- package/addon/components/cf-field-value.hbs +22 -7
- package/addon/components/cf-field-value.js +12 -36
- package/addon/components/cf-field.hbs +42 -9
- package/addon/components/cf-field.js +41 -17
- package/addon/components/cf-form-wrapper.hbs +4 -1
- package/addon/components/cf-form.hbs +6 -1
- package/addon/components/document-validity.js +16 -1
- package/addon/gql/fragments/field.graphql +32 -7
- package/addon/gql/mutations/save-document-files-answer.graphql +9 -0
- package/addon/gql/queries/document-forms.graphql +1 -1
- package/addon/gql/queries/dynamic-options.graphql +4 -4
- package/addon/gql/queries/{fileanswer-info.graphql → filesanswer-info.graphql} +4 -4
- package/addon/helpers/format-graphql-error.js +21 -0
- package/addon/helpers/get-widget.js +16 -2
- package/addon/instance-initializers/form-widget-overrides.js +52 -0
- package/addon/lib/document.js +9 -1
- package/addon/lib/field.js +96 -59
- package/addon/lib/navigation.js +3 -1
- package/addon/lib/question.js +18 -5
- package/addon/modifiers/autoresize.js +14 -0
- package/addon/services/caluma-store.js +2 -0
- package/app/components/cf-field/{input/file.js → hint.js} +1 -1
- package/app/components/cf-field/input/files.js +1 -0
- package/app/helpers/format-graphql-error.js +1 -0
- package/app/helpers/get-widget.js +1 -4
- package/app/instance-initializers/form-widget-overrides.js +4 -0
- package/app/modifiers/autoresize.js +1 -0
- package/app/styles/@projectcaluma/ember-form.scss +5 -15
- package/app/styles/_flatpickr.scss +47 -0
- package/blueprints/@projectcaluma/ember-form/index.js +1 -1
- package/index.js +12 -0
- package/package.json +47 -37
- package/translations/de.yaml +6 -6
- package/translations/en.yaml +6 -6
- package/translations/fr.yaml +6 -6
- package/addon/components/cf-field/input/file.hbs +0 -32
- package/addon/components/cf-field/input/file.js +0 -89
- package/addon/components/cf-field/label.js +0 -11
- package/addon/gql/mutations/remove-answer.graphql +0 -7
- package/addon/gql/mutations/save-document-file-answer.graphql +0 -9
- package/addon/instance-initializers/setup-pikaday-i18n.js +0 -35
- package/config/environment.js +0 -5
@@ -1,5 +1,5 @@
|
|
1
|
-
import {
|
2
|
-
import {
|
1
|
+
import { action } from "@ember/object";
|
2
|
+
import { macroCondition, isTesting } from "@embroider/macros";
|
3
3
|
import Component from "@glimmer/component";
|
4
4
|
import { timeout, restartableTask } from "ember-concurrency";
|
5
5
|
|
@@ -9,7 +9,7 @@ import { hasQuestionType } from "@projectcaluma/ember-core/helpers/has-question-
|
|
9
9
|
* Component to display a label and input for a certain field of a document.
|
10
10
|
*
|
11
11
|
* ```hbs
|
12
|
-
*
|
12
|
+
* <CfField @field={{this.someField}} />
|
13
13
|
* ```
|
14
14
|
*
|
15
15
|
* You can disable the field by passing `disabled=true`.
|
@@ -18,6 +18,16 @@ import { hasQuestionType } from "@projectcaluma/ember-core/helpers/has-question-
|
|
18
18
|
* @argument {Field} field The field data model to render
|
19
19
|
*/
|
20
20
|
export default class CfFieldComponent extends Component {
|
21
|
+
@action
|
22
|
+
registerComponent() {
|
23
|
+
this.args.field._components.add(this);
|
24
|
+
}
|
25
|
+
|
26
|
+
@action
|
27
|
+
unregisterComponent() {
|
28
|
+
this.args.field._components.delete(this);
|
29
|
+
}
|
30
|
+
|
21
31
|
get visible() {
|
22
32
|
return (
|
23
33
|
!this.args.field?.hidden &&
|
@@ -36,35 +46,49 @@ export default class CfFieldComponent extends Component {
|
|
36
46
|
return !hasQuestionType(this.args.field?.question, "action-button");
|
37
47
|
}
|
38
48
|
|
49
|
+
get hintTextVisible() {
|
50
|
+
return !hasQuestionType(
|
51
|
+
this.args.field?.question,
|
52
|
+
"action-button",
|
53
|
+
"static",
|
54
|
+
"form"
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
39
58
|
get saveIndicatorVisible() {
|
40
59
|
return !hasQuestionType(this.args.field?.question, "action-button");
|
41
60
|
}
|
42
61
|
|
43
62
|
/**
|
44
|
-
* Task to save a field. This will set the passed value to the answer and
|
45
|
-
*
|
63
|
+
* Task to save a field. This will set the passed value to the answer and save
|
64
|
+
* the field to the API after a timeout of 500 milliseconds which intends to
|
65
|
+
* reduce the amount of saved values when changed rapidly.
|
46
66
|
*
|
47
67
|
* @method save
|
48
|
-
* @param {String|Number|String[]} value
|
68
|
+
* @param {String|Number|String[]} value The new value to save to the field
|
69
|
+
* @param {Boolean} bypassTimeout Whether to bypass the timeout
|
49
70
|
*/
|
50
71
|
@restartableTask
|
51
|
-
*save(value) {
|
52
|
-
|
53
|
-
|
72
|
+
*save(value, bypassTimeout = false) {
|
73
|
+
if (typeof this.args.onSave === "function") {
|
74
|
+
return yield this.args.onSave(this.args.field, value);
|
75
|
+
}
|
54
76
|
|
55
77
|
/* istanbul ignore next */
|
56
|
-
if (
|
57
|
-
|
78
|
+
if (macroCondition(isTesting())) {
|
79
|
+
// no timeout
|
80
|
+
} else {
|
81
|
+
if (!bypassTimeout) {
|
82
|
+
yield timeout(500);
|
83
|
+
}
|
58
84
|
}
|
59
85
|
|
60
|
-
|
86
|
+
if (this.args.field.answer) {
|
87
|
+
this.args.field.answer.value = value;
|
88
|
+
}
|
61
89
|
|
62
90
|
yield this.args.field.validate.perform();
|
63
91
|
|
64
|
-
|
65
|
-
return yield this.args.field.save.unlinked().perform();
|
66
|
-
} catch (e) {
|
67
|
-
// that's ok
|
68
|
-
}
|
92
|
+
return yield this.args.field.save.unlinked().perform();
|
69
93
|
}
|
70
94
|
}
|
@@ -1,6 +1,8 @@
|
|
1
1
|
{{#let
|
2
2
|
(component
|
3
|
-
(
|
3
|
+
(ensure-safe-component
|
4
|
+
(get-widget @fieldset.field.question @fieldset.form default="cf-form")
|
5
|
+
)
|
4
6
|
)
|
5
7
|
as |FormComponent|
|
6
8
|
}}
|
@@ -9,5 +11,6 @@
|
|
9
11
|
@fieldset={{@fieldset}}
|
10
12
|
@context={{@context}}
|
11
13
|
@disabled={{@disabled}}
|
14
|
+
@onSave={{@onSave}}
|
12
15
|
/>
|
13
16
|
{{/let}}
|
@@ -3,7 +3,12 @@
|
|
3
3
|
{{#if (has-block)}}
|
4
4
|
{{yield field}}
|
5
5
|
{{else}}
|
6
|
-
<CfField
|
6
|
+
<CfField
|
7
|
+
@field={{field}}
|
8
|
+
@disabled={{@disabled}}
|
9
|
+
@context={{@context}}
|
10
|
+
@onSave={{@onSave}}
|
11
|
+
/>
|
7
12
|
{{/if}}
|
8
13
|
{{/each}}
|
9
14
|
</form>
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { action } from "@ember/object";
|
2
2
|
import Component from "@glimmer/component";
|
3
3
|
import { restartableTask } from "ember-concurrency";
|
4
|
+
import { cached } from "tracked-toolbox";
|
4
5
|
|
5
6
|
/**
|
6
7
|
* Component to check the validity of a document
|
@@ -31,12 +32,26 @@ export default class DocumentValidity extends Component {
|
|
31
32
|
* @argument {Boolean} validateOnEnter
|
32
33
|
*/
|
33
34
|
|
35
|
+
@cached
|
34
36
|
get isValid() {
|
35
|
-
return this.args.document.fields
|
37
|
+
return this.args.document.fields
|
38
|
+
.filter((f) => !f.hidden)
|
39
|
+
.every((f) => f.isValid);
|
36
40
|
}
|
37
41
|
|
38
42
|
@restartableTask
|
39
43
|
*_validate() {
|
44
|
+
const saveTasks = this.args.document.fields
|
45
|
+
.flatMap((field) => [
|
46
|
+
...[...(field._components ?? [])].map((c) => c.save.last),
|
47
|
+
field.save?.last,
|
48
|
+
])
|
49
|
+
.filter(Boolean);
|
50
|
+
|
51
|
+
// Wait until all currently running save tasks in the UI and in the field
|
52
|
+
// itself are finished
|
53
|
+
yield Promise.all(saveTasks);
|
54
|
+
|
40
55
|
for (const field of this.args.document.fields) {
|
41
56
|
yield field.validate.linked().perform();
|
42
57
|
}
|
@@ -1,8 +1,3 @@
|
|
1
|
-
# We can not symlink this file so an exact copy exists in another package:
|
2
|
-
# packages/form-builder/addon/gql/fragments/field.graphql
|
3
|
-
#
|
4
|
-
# When changing this file the other must also receive the same changes.
|
5
|
-
|
6
1
|
fragment SimpleQuestion on Question {
|
7
2
|
id
|
8
3
|
slug
|
@@ -19,6 +14,16 @@ fragment SimpleQuestion on Question {
|
|
19
14
|
value
|
20
15
|
}
|
21
16
|
placeholder
|
17
|
+
formatValidators {
|
18
|
+
edges {
|
19
|
+
node {
|
20
|
+
slug
|
21
|
+
regex
|
22
|
+
errorMsg
|
23
|
+
}
|
24
|
+
}
|
25
|
+
}
|
26
|
+
hintText
|
22
27
|
}
|
23
28
|
... on TextareaQuestion {
|
24
29
|
textareaMinLength: minLength
|
@@ -28,6 +33,16 @@ fragment SimpleQuestion on Question {
|
|
28
33
|
value
|
29
34
|
}
|
30
35
|
placeholder
|
36
|
+
formatValidators {
|
37
|
+
edges {
|
38
|
+
node {
|
39
|
+
slug
|
40
|
+
regex
|
41
|
+
errorMsg
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
hintText
|
31
46
|
}
|
32
47
|
... on IntegerQuestion {
|
33
48
|
integerMinValue: minValue
|
@@ -37,6 +52,7 @@ fragment SimpleQuestion on Question {
|
|
37
52
|
value
|
38
53
|
}
|
39
54
|
placeholder
|
55
|
+
hintText
|
40
56
|
}
|
41
57
|
... on FloatQuestion {
|
42
58
|
floatMinValue: minValue
|
@@ -46,6 +62,7 @@ fragment SimpleQuestion on Question {
|
|
46
62
|
value
|
47
63
|
}
|
48
64
|
placeholder
|
65
|
+
hintText
|
49
66
|
}
|
50
67
|
... on ChoiceQuestion {
|
51
68
|
choiceOptions: options {
|
@@ -62,6 +79,7 @@ fragment SimpleQuestion on Question {
|
|
62
79
|
id
|
63
80
|
value
|
64
81
|
}
|
82
|
+
hintText
|
65
83
|
}
|
66
84
|
... on MultipleChoiceQuestion {
|
67
85
|
multipleChoiceOptions: options {
|
@@ -78,18 +96,24 @@ fragment SimpleQuestion on Question {
|
|
78
96
|
id
|
79
97
|
value
|
80
98
|
}
|
99
|
+
hintText
|
81
100
|
}
|
82
101
|
... on DateQuestion {
|
83
102
|
dateDefaultAnswer: defaultAnswer {
|
84
103
|
id
|
85
104
|
value
|
86
105
|
}
|
106
|
+
hintText
|
87
107
|
}
|
88
108
|
... on StaticQuestion {
|
89
109
|
staticContent
|
90
110
|
}
|
91
111
|
... on CalculatedFloatQuestion {
|
92
112
|
calcExpression
|
113
|
+
hintText
|
114
|
+
}
|
115
|
+
... on FilesQuestion {
|
116
|
+
hintText
|
93
117
|
}
|
94
118
|
... on ActionButtonQuestion {
|
95
119
|
action
|
@@ -112,6 +136,7 @@ fragment FieldTableQuestion on Question {
|
|
112
136
|
}
|
113
137
|
}
|
114
138
|
}
|
139
|
+
hintText
|
115
140
|
tableDefaultAnswer: defaultAnswer {
|
116
141
|
id
|
117
142
|
value {
|
@@ -204,8 +229,8 @@ fragment SimpleAnswer on Answer {
|
|
204
229
|
... on ListAnswer {
|
205
230
|
listValue: value
|
206
231
|
}
|
207
|
-
... on
|
208
|
-
|
232
|
+
... on FilesAnswer {
|
233
|
+
filesValue: value {
|
209
234
|
id
|
210
235
|
uploadUrl
|
211
236
|
downloadUrl
|
@@ -1,11 +1,11 @@
|
|
1
|
-
query DynamicOptions($question: String
|
2
|
-
allQuestions(filter: [{
|
1
|
+
query DynamicOptions($question: String!, $context: JSONString) {
|
2
|
+
allQuestions(filter: [{ slugs: [$question] }], first: 1) {
|
3
3
|
edges {
|
4
4
|
node {
|
5
5
|
id
|
6
6
|
slug
|
7
7
|
... on DynamicChoiceQuestion {
|
8
|
-
dynamicChoiceOptions: options {
|
8
|
+
dynamicChoiceOptions: options(context: $context) {
|
9
9
|
edges {
|
10
10
|
node {
|
11
11
|
slug
|
@@ -15,7 +15,7 @@ query DynamicOptions($question: String!) {
|
|
15
15
|
}
|
16
16
|
}
|
17
17
|
... on DynamicMultipleChoiceQuestion {
|
18
|
-
dynamicMultipleChoiceOptions: options {
|
18
|
+
dynamicMultipleChoiceOptions: options(context: $context) {
|
19
19
|
edges {
|
20
20
|
node {
|
21
21
|
slug
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { helper } from "@ember/component/helper";
|
2
|
+
|
3
|
+
export function formatGraphqlErrorObject(error) {
|
4
|
+
try {
|
5
|
+
const path = error.path.join(".");
|
6
|
+
const { line, column } = error.locations[error.locations.length - 1];
|
7
|
+
|
8
|
+
return `${path}:${line}:${column}: ${error.message}`;
|
9
|
+
} catch (e) {
|
10
|
+
return null;
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
export function formatGraphqlError(error) {
|
15
|
+
return (
|
16
|
+
error?.errors?.map(formatGraphqlErrorObject).filter(Boolean).join("\n") ??
|
17
|
+
""
|
18
|
+
);
|
19
|
+
}
|
20
|
+
|
21
|
+
export default helper(([error]) => formatGraphqlError(error));
|
@@ -1,6 +1,15 @@
|
|
1
1
|
import Helper from "@ember/component/helper";
|
2
2
|
import { warn } from "@ember/debug";
|
3
3
|
import { inject as service } from "@ember/service";
|
4
|
+
import { ensureSafeComponent } from "@embroider/util";
|
5
|
+
|
6
|
+
import InputComponent from "@projectcaluma/ember-form/components/cf-field/input";
|
7
|
+
import FormComponent from "@projectcaluma/ember-form/components/cf-form";
|
8
|
+
|
9
|
+
const DEFAULT_WIDGETS = {
|
10
|
+
"cf-field/input": InputComponent,
|
11
|
+
"cf-form": FormComponent,
|
12
|
+
};
|
4
13
|
|
5
14
|
/**
|
6
15
|
* Helper for getting the right widget.
|
@@ -42,9 +51,14 @@ export default class GetWidgetHelper extends Helper {
|
|
42
51
|
{ id: "ember-caluma.unregistered-override" }
|
43
52
|
);
|
44
53
|
|
45
|
-
if (override)
|
54
|
+
if (override) {
|
55
|
+
return ensureSafeComponent(
|
56
|
+
override.componentClass ?? override.component,
|
57
|
+
this
|
58
|
+
);
|
59
|
+
}
|
46
60
|
}
|
47
61
|
|
48
|
-
return defaultWidget;
|
62
|
+
return ensureSafeComponent(DEFAULT_WIDGETS[defaultWidget], this);
|
49
63
|
}
|
50
64
|
}
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import { setOwner } from "@ember/application";
|
2
|
+
import { inject as service } from "@ember/service";
|
3
|
+
|
4
|
+
import HiddenComponent from "@projectcaluma/ember-form/components/cf-field/input/hidden";
|
5
|
+
import PowerSelectComponent from "@projectcaluma/ember-form/components/cf-field/input/powerselect";
|
6
|
+
|
7
|
+
class HiddenOverride {
|
8
|
+
@service intl;
|
9
|
+
|
10
|
+
get label() {
|
11
|
+
return this.intl.t("caluma.form-builder.question.widgetOverrides.hidden");
|
12
|
+
}
|
13
|
+
|
14
|
+
component = "cf-field/input/hidden";
|
15
|
+
componentClass = HiddenComponent;
|
16
|
+
}
|
17
|
+
|
18
|
+
class PowerSelectOverride {
|
19
|
+
@service intl;
|
20
|
+
|
21
|
+
get label() {
|
22
|
+
return this.intl.t(
|
23
|
+
"caluma.form-builder.question.widgetOverrides.powerselect"
|
24
|
+
);
|
25
|
+
}
|
26
|
+
|
27
|
+
component = "cf-field/input/powerselect";
|
28
|
+
componentClass = PowerSelectComponent;
|
29
|
+
types = [
|
30
|
+
"ChoiceQuestion",
|
31
|
+
"MultipleChoiceQuestion",
|
32
|
+
"DynamicChoiceQuestion",
|
33
|
+
"DynamicMultipleChoiceQuestion",
|
34
|
+
];
|
35
|
+
}
|
36
|
+
|
37
|
+
export function initialize(appInstance) {
|
38
|
+
const options = appInstance.lookup("service:caluma-options");
|
39
|
+
|
40
|
+
const hiddenOverride = new HiddenOverride();
|
41
|
+
const powerSelectOverride = new PowerSelectOverride();
|
42
|
+
|
43
|
+
setOwner(hiddenOverride, appInstance);
|
44
|
+
setOwner(powerSelectOverride, appInstance);
|
45
|
+
|
46
|
+
options.registerComponentOverride(hiddenOverride);
|
47
|
+
options.registerComponentOverride(powerSelectOverride);
|
48
|
+
}
|
49
|
+
|
50
|
+
export default {
|
51
|
+
initialize,
|
52
|
+
};
|
package/addon/lib/document.js
CHANGED
@@ -18,7 +18,7 @@ const sum = (nums) => nums.reduce((num, base) => base + num, 0);
|
|
18
18
|
* @class Document
|
19
19
|
*/
|
20
20
|
export default class Document extends Base {
|
21
|
-
constructor({ raw, parentDocument, ...args }) {
|
21
|
+
constructor({ raw, parentDocument, dataSourceContext, ...args }) {
|
22
22
|
assert(
|
23
23
|
"A graphql document `raw` must be passed",
|
24
24
|
raw?.__typename === "Document"
|
@@ -27,6 +27,7 @@ export default class Document extends Base {
|
|
27
27
|
super({ raw, ...args });
|
28
28
|
|
29
29
|
this.parentDocument = parentDocument;
|
30
|
+
this.dataSourceContext = dataSourceContext;
|
30
31
|
|
31
32
|
this.pushIntoStore();
|
32
33
|
|
@@ -83,6 +84,13 @@ export default class Document extends Base {
|
|
83
84
|
*/
|
84
85
|
fieldsets = [];
|
85
86
|
|
87
|
+
/**
|
88
|
+
* Context object for data sources
|
89
|
+
*
|
90
|
+
* @property {Object} dataSourceContext
|
91
|
+
*/
|
92
|
+
dataSourceContext = null;
|
93
|
+
|
86
94
|
/**
|
87
95
|
* The primary key of the document.
|
88
96
|
*
|