@projectcaluma/ember-form 11.0.0-beta.9 → 11.0.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/CHANGELOG.md +147 -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 +23 -18
- 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 +22 -25
- package/addon/components/cf-field/input/date.js +37 -22
- 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/table.hbs +3 -2
- 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 +10 -8
- package/addon/components/cf-field-value.js +6 -5
- package/addon/components/cf-field.hbs +30 -5
- package/addon/components/cf-field.js +24 -15
- package/addon/components/cf-form-wrapper.hbs +4 -1
- package/addon/components/cf-form.hbs +6 -1
- package/addon/gql/fragments/field.graphql +14 -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 +49 -46
- 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 +49 -40
- 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/config/environment.js +0 -5
@@ -0,0 +1,113 @@
|
|
1
|
+
import { action } from "@ember/object";
|
2
|
+
import { inject as service } from "@ember/service";
|
3
|
+
import { macroCondition, isTesting } from "@embroider/macros";
|
4
|
+
import Component from "@glimmer/component";
|
5
|
+
import { queryManager } from "ember-apollo-client";
|
6
|
+
import fetch from "fetch";
|
7
|
+
|
8
|
+
import getFilesAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/filesanswer-info.graphql";
|
9
|
+
|
10
|
+
export default class CfFieldInputFilesComponent extends Component {
|
11
|
+
@service intl;
|
12
|
+
|
13
|
+
@queryManager apollo;
|
14
|
+
|
15
|
+
get files() {
|
16
|
+
return this.args.field?.answer?.value;
|
17
|
+
}
|
18
|
+
|
19
|
+
@action
|
20
|
+
async download(fileId) {
|
21
|
+
if (!fileId) {
|
22
|
+
return;
|
23
|
+
}
|
24
|
+
const answers = await this.apollo.query(
|
25
|
+
{
|
26
|
+
query: getFilesAnswerInfoQuery,
|
27
|
+
variables: { id: this.args.field.answer.raw.id },
|
28
|
+
fetchPolicy: "network-only",
|
29
|
+
},
|
30
|
+
"node.value"
|
31
|
+
);
|
32
|
+
const { downloadUrl } =
|
33
|
+
answers.find((file) =>
|
34
|
+
// the testing graph-ql setup does a base64 encoding of `__typename: fileID`
|
35
|
+
macroCondition(isTesting())
|
36
|
+
? file.id === fileId ||
|
37
|
+
atob(file.id).substring(file.__typename.length + 1) === fileId
|
38
|
+
: file.id === fileId
|
39
|
+
) ?? {};
|
40
|
+
if (downloadUrl) {
|
41
|
+
window.open(downloadUrl, "_blank");
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
@action
|
46
|
+
async save({ target }) {
|
47
|
+
// store the old list of files
|
48
|
+
// unwrap files from FileList construct
|
49
|
+
let newFiles = Array.from(target.files).map((file) => ({
|
50
|
+
name: file.name,
|
51
|
+
value: file,
|
52
|
+
}));
|
53
|
+
|
54
|
+
const fileList = [...(this.files || []), ...newFiles];
|
55
|
+
|
56
|
+
if (newFiles.length === 0) {
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
|
60
|
+
// trigger save action for file list of old and new files with
|
61
|
+
// reduces properties to match gql format
|
62
|
+
const { filesValue: savedAnswerValue } = await this.args.onSave(
|
63
|
+
fileList.map(({ name, id }) => ({ name, id }))
|
64
|
+
);
|
65
|
+
|
66
|
+
try {
|
67
|
+
// iterate over list of new files and enrich with graphql answer values
|
68
|
+
newFiles = newFiles.map((file) => ({
|
69
|
+
...savedAnswerValue.find(
|
70
|
+
(value) =>
|
71
|
+
file.name === value.name &&
|
72
|
+
!fileList.find((file) => file.id === value.id)
|
73
|
+
),
|
74
|
+
value: file.value,
|
75
|
+
}));
|
76
|
+
|
77
|
+
const uploadFunction = async (file) => {
|
78
|
+
const response = await fetch(file.uploadUrl, {
|
79
|
+
method: "PUT",
|
80
|
+
body: file.value,
|
81
|
+
});
|
82
|
+
if (!response.ok) {
|
83
|
+
throw new Error();
|
84
|
+
}
|
85
|
+
return response;
|
86
|
+
};
|
87
|
+
|
88
|
+
// upload the actual file to data storage
|
89
|
+
await Promise.all(newFiles.map((file) => uploadFunction(file)));
|
90
|
+
|
91
|
+
this.args.field.answer.value = savedAnswerValue;
|
92
|
+
} catch (error) {
|
93
|
+
await this.args.onSave([]);
|
94
|
+
this.args.field._errors = [{ type: "uploadFailed" }];
|
95
|
+
} finally {
|
96
|
+
// eslint-disable-next-line require-atomic-updates
|
97
|
+
target.value = "";
|
98
|
+
}
|
99
|
+
}
|
100
|
+
|
101
|
+
@action
|
102
|
+
async delete(fileId) {
|
103
|
+
const remainingFiles = this.files
|
104
|
+
.filter((file) => file.id !== fileId)
|
105
|
+
.map(({ name, id }) => ({ name, id }));
|
106
|
+
|
107
|
+
try {
|
108
|
+
await this.args.onSave(remainingFiles);
|
109
|
+
} catch (error) {
|
110
|
+
this.args.field._errors = [{ type: "deleteFailed" }];
|
111
|
+
}
|
112
|
+
}
|
113
|
+
}
|
@@ -1,29 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
{{
|
26
|
-
|
27
|
-
|
28
|
-
</DynamicSelect>
|
29
|
-
{{/let}}
|
1
|
+
<this.selectComponent
|
2
|
+
@options={{@field.options}}
|
3
|
+
@selected={{@field.selected}}
|
4
|
+
@disabled={{@disabled}}
|
5
|
+
@allowClear={{true}}
|
6
|
+
@preventScroll={{true}}
|
7
|
+
@searchEnabled={{this.searchEnabled}}
|
8
|
+
@searchField="label"
|
9
|
+
@triggerId={{@field.pk}}
|
10
|
+
@renderInPlace={{true}}
|
11
|
+
@placeholder={{this.placeholder}}
|
12
|
+
@loadingMessage={{t "caluma.form.power-select.options-loading"}}
|
13
|
+
@searchMessage={{t "caluma.form.power-select.options-empty"}}
|
14
|
+
@searchPlaceholder={{t "caluma.form.power-select.search-placeholder"}}
|
15
|
+
@noMatchesMessage={{t "caluma.form.power-select.search-empty"}}
|
16
|
+
@onChange={{this.change}}
|
17
|
+
as |option|
|
18
|
+
>
|
19
|
+
{{#if (and option.disabled (not @disabled))}}
|
20
|
+
<del
|
21
|
+
class="uk-text-muted"
|
22
|
+
title={{t "caluma.form.optionNotAvailable"}}
|
23
|
+
>{{option.label}}</del>
|
24
|
+
{{else}}
|
25
|
+
{{option.label}}
|
26
|
+
{{/if}}
|
27
|
+
</this.selectComponent>
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import { getOwner } from "@ember/application";
|
2
2
|
import { action } from "@ember/object";
|
3
3
|
import { inject as service } from "@ember/service";
|
4
|
+
import { ensureSafeComponent } from "@embroider/util";
|
4
5
|
import Component from "@glimmer/component";
|
6
|
+
import PowerSelectComponent from "ember-power-select/components/power-select";
|
7
|
+
import PowerSelectMultipleComponent from "ember-power-select/components/power-select-multiple";
|
5
8
|
|
6
9
|
/**
|
7
10
|
* Dropdown component for the single and multiple choice question type
|
@@ -16,8 +19,11 @@ export default class CfFieldInputPowerselectComponent extends Component {
|
|
16
19
|
return this.args.field?.question.isMultipleChoice;
|
17
20
|
}
|
18
21
|
|
19
|
-
get
|
20
|
-
return
|
22
|
+
get selectComponent() {
|
23
|
+
return ensureSafeComponent(
|
24
|
+
this.multiple ? PowerSelectMultipleComponent : PowerSelectComponent,
|
25
|
+
this
|
26
|
+
);
|
21
27
|
}
|
22
28
|
|
23
29
|
get searchEnabled() {
|
@@ -1,5 +1,5 @@
|
|
1
1
|
{{#each @field.options as |option i|}}
|
2
|
-
{{#if (gt i 0)}}<br />{{/if}}
|
2
|
+
{{#if (and (gt i 0) (not @field.raw.question.meta.vertical))}}<br />{{/if}}
|
3
3
|
<label
|
4
4
|
class="{{if @field.isInvalid 'uk-form-danger'}}
|
5
5
|
{{if @field.raw.question.meta.vertical 'uk-margin-large-right'}}"
|
@@ -10,7 +10,7 @@
|
|
10
10
|
name={{@field.pk}}
|
11
11
|
value={{option.slug}}
|
12
12
|
checked={{eq option.slug @field.answer.value}}
|
13
|
-
disabled={{@disabled}}
|
13
|
+
disabled={{or option.disabled @disabled}}
|
14
14
|
{{on "change" (fn @onSave option.slug)}}
|
15
15
|
/>
|
16
16
|
{{#if (and option.disabled (not @disabled))}}
|
@@ -29,7 +29,7 @@
|
|
29
29
|
@color="link"
|
30
30
|
@onClick={{fn this.edit document}}
|
31
31
|
title={{t "caluma.form.edit"}}
|
32
|
-
class="uk-flex-inline uk-margin-small-left"
|
32
|
+
class="uk-flex-inline uk-margin-small-left table-controls"
|
33
33
|
data-test-edit-row
|
34
34
|
>
|
35
35
|
<UkIcon @icon="pencil" />
|
@@ -40,7 +40,7 @@
|
|
40
40
|
@color="link"
|
41
41
|
@onClick={{fn (perform this.delete) document}}
|
42
42
|
title={{t "caluma.form.delete"}}
|
43
|
-
class="uk-flex-inline uk-margin-small-left"
|
43
|
+
class="uk-flex-inline uk-margin-small-left table-controls"
|
44
44
|
data-test-delete-row
|
45
45
|
>
|
46
46
|
<UkIcon @icon="trash" />
|
@@ -83,6 +83,7 @@
|
|
83
83
|
@document={{this.documentToEdit}}
|
84
84
|
@fieldset={{object-at 0 this.documentToEdit.fieldsets}}
|
85
85
|
@disabled={{@disabled}}
|
86
|
+
@context={{@context}}
|
86
87
|
/>
|
87
88
|
</modal.body>
|
88
89
|
|
@@ -5,22 +5,12 @@ import Component from "@glimmer/component";
|
|
5
5
|
import { tracked } from "@glimmer/tracking";
|
6
6
|
import { queryManager } from "ember-apollo-client";
|
7
7
|
import { dropTask } from "ember-concurrency";
|
8
|
-
import
|
8
|
+
import { confirm } from "ember-uikit";
|
9
9
|
|
10
10
|
import removeDocumentMutation from "@projectcaluma/ember-form/gql/mutations/remove-document.graphql";
|
11
11
|
import saveDocumentMutation from "@projectcaluma/ember-form/gql/mutations/save-document.graphql";
|
12
12
|
import { parseDocument } from "@projectcaluma/ember-form/lib/parsers";
|
13
13
|
|
14
|
-
async function confirm(text) {
|
15
|
-
try {
|
16
|
-
await UIkit.modal.confirm(text);
|
17
|
-
|
18
|
-
return true;
|
19
|
-
} catch (error) {
|
20
|
-
return false;
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
14
|
export default class CfFieldInputTableComponent extends Component {
|
25
15
|
@service notification;
|
26
16
|
@service intl;
|
@@ -1,21 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
)
|
10
|
-
'uk-flex'
|
11
|
-
}}"
|
12
|
-
>
|
13
|
-
<InputComponent
|
14
|
-
@field={{@field}}
|
15
|
-
@disabled={{@disabled}}
|
16
|
-
@onSave={{@onSave}}
|
17
|
-
@context={{@context}}
|
18
|
-
/>
|
19
|
-
</div>
|
20
|
-
{{/let}}
|
21
|
-
{{/if}}
|
1
|
+
<div class="uk-form-controls">
|
2
|
+
<this.inputComponent
|
3
|
+
@field={{@field}}
|
4
|
+
@disabled={{@disabled}}
|
5
|
+
@onSave={{@onSave}}
|
6
|
+
@context={{@context}}
|
7
|
+
/>
|
8
|
+
</div>
|
@@ -1,12 +1,33 @@
|
|
1
|
-
import {
|
1
|
+
import { ensureSafeComponent } from "@embroider/util";
|
2
2
|
import Component from "@glimmer/component";
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
import ActionButtonComponent from "@projectcaluma/ember-form/components/cf-field/input/action-button";
|
5
|
+
import CheckboxComponent from "@projectcaluma/ember-form/components/cf-field/input/checkbox";
|
6
|
+
import DateComponent from "@projectcaluma/ember-form/components/cf-field/input/date";
|
7
|
+
import FilesComponent from "@projectcaluma/ember-form/components/cf-field/input/files";
|
8
|
+
import FloatComponent from "@projectcaluma/ember-form/components/cf-field/input/float";
|
9
|
+
import IntegerComponent from "@projectcaluma/ember-form/components/cf-field/input/integer";
|
10
|
+
import RadioComponent from "@projectcaluma/ember-form/components/cf-field/input/radio";
|
11
|
+
import StaticComponent from "@projectcaluma/ember-form/components/cf-field/input/static";
|
12
|
+
import TableComponent from "@projectcaluma/ember-form/components/cf-field/input/table";
|
13
|
+
import TextComponent from "@projectcaluma/ember-form/components/cf-field/input/text";
|
14
|
+
import TextareaComponent from "@projectcaluma/ember-form/components/cf-field/input/textarea";
|
15
|
+
|
16
|
+
const COMPONENT_MAPPING = {
|
17
|
+
ActionButtonQuestion: ActionButtonComponent,
|
18
|
+
CalculatedFloatQuestion: FloatComponent,
|
19
|
+
ChoiceQuestion: RadioComponent,
|
20
|
+
DateQuestion: DateComponent,
|
21
|
+
DynamicChoiceQuestion: RadioComponent,
|
22
|
+
DynamicMultipleChoiceQuestion: CheckboxComponent,
|
23
|
+
FilesQuestion: FilesComponent,
|
24
|
+
FloatQuestion: FloatComponent,
|
25
|
+
IntegerQuestion: IntegerComponent,
|
26
|
+
MultipleChoiceQuestion: CheckboxComponent,
|
27
|
+
StaticQuestion: StaticComponent,
|
28
|
+
TableQuestion: TableComponent,
|
29
|
+
TextareaQuestion: TextareaComponent,
|
30
|
+
TextQuestion: TextComponent,
|
10
31
|
};
|
11
32
|
|
12
33
|
/**
|
@@ -16,17 +37,14 @@ const mapping = {
|
|
16
37
|
*/
|
17
38
|
export default class CfFieldInputComponent extends Component {
|
18
39
|
/**
|
19
|
-
* The input component
|
40
|
+
* The input component
|
20
41
|
*
|
21
|
-
* @property {
|
42
|
+
* @property {Component} inputComponent
|
22
43
|
* @accessor
|
23
44
|
*/
|
24
|
-
get
|
45
|
+
get inputComponent() {
|
25
46
|
const typename = this.args.field?.question.raw.__typename;
|
26
47
|
|
27
|
-
return (
|
28
|
-
typename &&
|
29
|
-
(mapping[typename] || dasherize(typename.replace(/Question$/, "")))
|
30
|
-
);
|
48
|
+
return ensureSafeComponent(COMPONENT_MAPPING[typename], this);
|
31
49
|
}
|
32
50
|
}
|
@@ -1,11 +1,13 @@
|
|
1
|
-
<label class="uk-form-label" for={{@field.pk}}>
|
1
|
+
<label class="uk-form-label" for={{@field.pk}} ...attributes>
|
2
2
|
<span class="uk-text-bold">
|
3
3
|
{{@field.question.raw.label}}
|
4
4
|
</span>
|
5
5
|
|
6
|
-
{{#if
|
6
|
+
{{#if (and (not @field.question.isCalculated) @field.optional)}}
|
7
7
|
<span
|
8
8
|
class="uk-margin-small-left uk-text-muted uk-text-lowercase uk-text-normal"
|
9
9
|
>({{t "caluma.form.optional"}})</span>
|
10
10
|
{{/if}}
|
11
|
+
|
12
|
+
{{yield}}
|
11
13
|
</label>
|
@@ -1,8 +1,10 @@
|
|
1
1
|
{{#if (has-question-type @field.question "choice" "dynamic-choice")}}
|
2
2
|
{{@field.selected.label}}
|
3
|
-
{{else if
|
4
|
-
|
5
|
-
|
3
|
+
{{else if
|
4
|
+
(has-question-type
|
5
|
+
@field.question "multiple-choice" "multiple-dynamic-choice"
|
6
|
+
)
|
7
|
+
}}
|
6
8
|
{{#each @field.selected as |opt i|}}{{if (gt i 0) ", "}}{{opt.label}}{{/each}}
|
7
9
|
{{else if (has-question-type @field.question "date")}}
|
8
10
|
{{format-date
|
@@ -11,14 +13,14 @@
|
|
11
13
|
month="2-digit"
|
12
14
|
year="numeric"
|
13
15
|
}}
|
14
|
-
{{else if (has-question-type @field.question "
|
15
|
-
{{#
|
16
|
+
{{else if (has-question-type @field.question "files")}}
|
17
|
+
{{#each @field.answer.value as |file|}}
|
16
18
|
<UkButton
|
17
19
|
@color="link"
|
18
|
-
@label={{
|
19
|
-
@onClick={{fn this.download
|
20
|
+
@label={{file.name}}
|
21
|
+
@onClick={{fn this.download file.id}}
|
20
22
|
/>
|
21
|
-
{{/
|
23
|
+
{{/each}}
|
22
24
|
{{else}}
|
23
25
|
{{@field.answer.value}}
|
24
26
|
{{/if}}
|
@@ -2,22 +2,23 @@ import { action } from "@ember/object";
|
|
2
2
|
import Component from "@glimmer/component";
|
3
3
|
import { queryManager } from "ember-apollo-client";
|
4
4
|
|
5
|
-
import
|
5
|
+
import getFilesAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/filesanswer-info.graphql";
|
6
6
|
|
7
7
|
export default class CfFieldValueComponent extends Component {
|
8
8
|
@queryManager apollo;
|
9
9
|
|
10
10
|
@action
|
11
11
|
async download(id) {
|
12
|
-
const
|
12
|
+
const files = await this.apollo.query(
|
13
13
|
{
|
14
|
-
query:
|
15
|
-
variables: { id },
|
14
|
+
query: getFilesAnswerInfoQuery,
|
15
|
+
variables: { id: this.args.field.answer.raw.id },
|
16
16
|
fetchPolicy: "network-only",
|
17
17
|
},
|
18
|
-
"node.
|
18
|
+
"node.value"
|
19
19
|
);
|
20
20
|
|
21
|
+
const { downloadUrl } = files?.find((file) => file.id === id);
|
21
22
|
if (downloadUrl) {
|
22
23
|
window.open(downloadUrl, "_blank");
|
23
24
|
}
|
@@ -9,12 +9,15 @@
|
|
9
9
|
{{will-destroy this.unregisterComponent}}
|
10
10
|
>
|
11
11
|
{{#if this.labelVisible}}
|
12
|
-
<CfField::
|
12
|
+
<CfField::Label @field={{@field}} />
|
13
13
|
{{/if}}
|
14
14
|
|
15
15
|
<div class="uk-flex">
|
16
16
|
<div class="uk-width-expand">
|
17
|
-
{{#let
|
17
|
+
{{#let
|
18
|
+
(component (ensure-safe-component (get-widget @field.question)))
|
19
|
+
as |FieldComponent|
|
20
|
+
}}
|
18
21
|
<FieldComponent
|
19
22
|
@field={{@field}}
|
20
23
|
@disabled={{@disabled}}
|
@@ -25,7 +28,7 @@
|
|
25
28
|
</div>
|
26
29
|
|
27
30
|
{{#if (and @field.question.raw.infoText this.infoTextVisible)}}
|
28
|
-
<CfField::
|
31
|
+
<CfField::Info @text={{@field.question.raw.infoText}} />
|
29
32
|
{{/if}}
|
30
33
|
|
31
34
|
{{#if this.saveIndicatorVisible}}
|
@@ -35,7 +38,25 @@
|
|
35
38
|
{{#if this.save.isRunning}}
|
36
39
|
<UkSpinner class="uk-animation-fade" />
|
37
40
|
{{else if (or this.save.last.isError @field.isInvalid)}}
|
38
|
-
<
|
41
|
+
<div class="uk-flex-inline">
|
42
|
+
<UkIcon
|
43
|
+
@icon="warning"
|
44
|
+
class="uk-animation-fade uk-text-danger"
|
45
|
+
/>
|
46
|
+
{{#if this.save.last.error}}
|
47
|
+
<div uk-dropdown="pos: bottom-left" class="uk-padding-small">
|
48
|
+
<div class="uk-alert uk-alert-danger uk-margin-small">
|
49
|
+
{{t "caluma.form.error.intro"}}
|
50
|
+
</div>
|
51
|
+
<p class="uk-text-meta uk-margin-small">
|
52
|
+
{{t "caluma.form.error.details"}}
|
53
|
+
</p>
|
54
|
+
<pre class="uk-margin-remove">
|
55
|
+
{{~format-graphql-error this.save.last.error~}}
|
56
|
+
</pre>
|
57
|
+
</div>
|
58
|
+
{{/if}}
|
59
|
+
</div>
|
39
60
|
{{else if this.save.last.isSuccessful}}
|
40
61
|
<UkIcon @icon="check" class="uk-animation-fade uk-text-success" />
|
41
62
|
{{/if}}
|
@@ -43,8 +64,12 @@
|
|
43
64
|
{{/if}}
|
44
65
|
</div>
|
45
66
|
|
67
|
+
{{#if (and @field.question.raw.hintText this.hintTextVisible)}}
|
68
|
+
<CfField::Hint @field={{@field}} />
|
69
|
+
{{/if}}
|
70
|
+
|
46
71
|
{{#if @field.errors.length}}
|
47
|
-
<CfField::
|
72
|
+
<CfField::Errors @field={{@field}} />
|
48
73
|
{{/if}}
|
49
74
|
</div>
|
50
75
|
{{/if}}
|
@@ -1,5 +1,5 @@
|
|
1
|
-
import { getOwner } from "@ember/application";
|
2
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`.
|
@@ -46,6 +46,15 @@ export default class CfFieldComponent extends Component {
|
|
46
46
|
return !hasQuestionType(this.args.field?.question, "action-button");
|
47
47
|
}
|
48
48
|
|
49
|
+
get hintTextVisible() {
|
50
|
+
return !hasQuestionType(
|
51
|
+
this.args.field?.question,
|
52
|
+
"action-button",
|
53
|
+
"static",
|
54
|
+
"form"
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
49
58
|
get saveIndicatorVisible() {
|
50
59
|
return !hasQuestionType(this.args.field?.question, "action-button");
|
51
60
|
}
|
@@ -56,16 +65,22 @@ export default class CfFieldComponent extends Component {
|
|
56
65
|
* reduce the amount of saved values when changed rapidly.
|
57
66
|
*
|
58
67
|
* @method save
|
59
|
-
* @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
|
60
70
|
*/
|
61
71
|
@restartableTask
|
62
|
-
*save(value) {
|
63
|
-
|
64
|
-
|
72
|
+
*save(value, bypassTimeout = false) {
|
73
|
+
if (typeof this.args.onSave === "function") {
|
74
|
+
return yield this.args.onSave(this.args.field, value);
|
75
|
+
}
|
65
76
|
|
66
77
|
/* istanbul ignore next */
|
67
|
-
if (
|
68
|
-
|
78
|
+
if (macroCondition(isTesting())) {
|
79
|
+
// no timeout
|
80
|
+
} else {
|
81
|
+
if (!bypassTimeout) {
|
82
|
+
yield timeout(500);
|
83
|
+
}
|
69
84
|
}
|
70
85
|
|
71
86
|
if (this.args.field.answer) {
|
@@ -74,12 +89,6 @@ export default class CfFieldComponent extends Component {
|
|
74
89
|
|
75
90
|
yield this.args.field.validate.perform();
|
76
91
|
|
77
|
-
|
78
|
-
// Save the new field value unlinked so the fields save task is not
|
79
|
-
// aborted when this component is destroyed
|
80
|
-
return yield this.args.field.save.unlinked().perform();
|
81
|
-
} catch (e) {
|
82
|
-
// The component was destroyed before the fields save task was finished
|
83
|
-
}
|
92
|
+
return yield this.args.field.save.unlinked().perform();
|
84
93
|
}
|
85
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>
|