@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.
@@ -1,44 +1,41 @@
1
1
  {{#if @documentId}}
2
- <div {{did-insert (perform this.fetchData)}} {{will-destroy this.teardown}}>
3
- {{#if this.fetchData.isRunning}}
4
- <div class="uk-text-center"><UkSpinner @ratio={{2}} /></div>
5
- {{else if this.document}}
6
- {{#let
7
- (hash
2
+ {{#if this.loading}}
3
+ <div class="uk-text-center"><UkSpinner @ratio={{2}} /></div>
4
+ {{else if this.document}}
5
+ {{#let
6
+ (hash
7
+ document=this.document
8
+ navigation=(component
9
+ "cf-navigation"
10
+ navigation=this.navigation
11
+ useAsHeading=(or @useAsHeading false)
12
+ headingBaseLevel=(or @headingBaseLevel 1)
13
+ )
14
+ pagination=(component "cf-pagination" navigation=this.navigation)
15
+ form=(component
16
+ "cf-form-wrapper"
8
17
  document=this.document
9
- navigation=(component
10
- "cf-navigation"
11
- navigation=this.navigation
12
- useAsHeading=(or @useAsHeading false)
13
- headingBaseLevel=(or @headingBaseLevel 1)
14
- )
15
- pagination=(component "cf-pagination" navigation=this.navigation)
16
- form=(component
17
- "cf-form-wrapper"
18
- document=this.document
19
- fieldset=this.fieldset
20
- context=@context
21
- disabled=@disabled
22
- )
18
+ fieldset=this.fieldset
19
+ context=@context
20
+ disabled=@disabled
23
21
  )
24
- as |content|
25
- }}
26
- {{#if (has-block)}}
27
- {{yield content}}
28
- {{else if (gt this.document.fieldsets.length 1)}}
29
- <div uk-grid>
30
- <div class="uk-width-1-1 uk-width-1-3@m"><content.navigation
31
- /></div>
32
- <div class="uk-width-1-1 uk-width-2-3@m">
33
- <content.form />
34
- <hr />
35
- <content.pagination />
36
- </div>
22
+ )
23
+ as |content|
24
+ }}
25
+ {{#if (has-block)}}
26
+ {{yield content}}
27
+ {{else if (gt this.document.fieldsets.length 1)}}
28
+ <div uk-grid>
29
+ <div class="uk-width-1-1 uk-width-1-3@m"><content.navigation /></div>
30
+ <div class="uk-width-1-1 uk-width-2-3@m">
31
+ <content.form />
32
+ <hr />
33
+ <content.pagination />
37
34
  </div>
38
- {{else}}
39
- <content.form />
40
- {{/if}}
41
- {{/let}}
42
- {{/if}}
43
- </div>
35
+ </div>
36
+ {{else}}
37
+ <content.form />
38
+ {{/if}}
39
+ {{/let}}
40
+ {{/if}}
44
41
  {{/if}}
@@ -1,9 +1,10 @@
1
1
  import { getOwner } from "@ember/application";
2
- import { action } from "@ember/object";
2
+ import { destroy, registerDestructor } from "@ember/destroyable";
3
3
  import { inject as service } from "@ember/service";
4
4
  import Component from "@glimmer/component";
5
5
  import { queryManager } from "ember-apollo-client";
6
- import { dropTask } from "ember-concurrency-decorators";
6
+ import { dropTask } from "ember-concurrency";
7
+ import { useTask } from "ember-resources";
7
8
 
8
9
  import getDocumentAnswersQuery from "@projectcaluma/ember-form/gql/queries/document-answers.graphql";
9
10
  import getDocumentFormsQuery from "@projectcaluma/ember-form/gql/queries/document-forms.graphql";
@@ -62,7 +63,7 @@ export default class CfContentComponent extends Component {
62
63
  * Can be used to pass "context" information from the outside through
63
64
  * to custom overrides.
64
65
  *
65
- * @argument {*} context
66
+ * @argument {Object} context
66
67
  */
67
68
 
68
69
  /**
@@ -71,17 +72,40 @@ export default class CfContentComponent extends Component {
71
72
  * @argument {Boolean} disabled
72
73
  */
73
74
 
75
+ /**
76
+ * Whether the form should be displayed as loading, this can be used to
77
+ * indicate a loading state if the application calling this component is
78
+ * loading additional data.
79
+ *
80
+ * @argument {Boolean} loading
81
+ */
82
+
74
83
  /**
75
84
  * The document to display
76
85
  *
77
86
  * @property {Document} document
78
87
  */
79
88
  get document() {
80
- return this.fetchData.lastSuccessful?.value.document;
89
+ return this.data.value?.document;
81
90
  }
82
91
 
92
+ /**
93
+ * The navigation to display
94
+ *
95
+ * @property {Document} document
96
+ */
83
97
  get navigation() {
84
- return this.fetchData.lastSuccessful?.value.navigation;
98
+ return this.data.value?.navigation;
99
+ }
100
+
101
+ /**
102
+ * Whether the component is in a loading state. This can be overwritten by
103
+ * passing `loading` as an argument
104
+ *
105
+ * @property {Boolean} loading
106
+ */
107
+ get loading() {
108
+ return this.args.loading || this.data.isRunning;
85
109
  }
86
110
 
87
111
  /**
@@ -109,9 +133,12 @@ export default class CfContentComponent extends Component {
109
133
  return fieldset;
110
134
  }
111
135
 
136
+ data = useTask(this, this.fetchData, () => [this.args.documentId]);
137
+
112
138
  @dropTask
113
139
  *fetchData() {
114
- this.teardown();
140
+ if (this.document) destroy(this.document);
141
+ if (this.navigation) destroy(this.navigation);
115
142
 
116
143
  if (!this.args.documentId) return;
117
144
 
@@ -133,20 +160,20 @@ export default class CfContentComponent extends Component {
133
160
  "allForms.edges"
134
161
  )).map(({ node }) => node);
135
162
 
136
- const document = getOwner(this)
137
- .factoryFor("caluma-model:document")
138
- .create({ raw: parseDocument({ ...answerDocument, form }) });
163
+ const owner = getOwner(this);
164
+ const Document = owner.factoryFor("caluma-model:document").class;
165
+ const Navigation = owner.factoryFor("caluma-model:navigation").class;
139
166
 
140
- const navigation = getOwner(this)
141
- .factoryFor("caluma-model:navigation")
142
- .create({ document });
167
+ const raw = parseDocument({ ...answerDocument, form });
143
168
 
144
- return { document, navigation };
145
- }
169
+ const document = new Document({ raw, owner });
170
+ const navigation = new Navigation({ document, owner });
146
171
 
147
- @action
148
- teardown() {
149
- if (this.document) this.document.destroy();
150
- if (this.navigation) this.navigation.destroy();
172
+ registerDestructor(this, () => {
173
+ destroy(document);
174
+ destroy(navigation);
175
+ });
176
+
177
+ return { document, navigation };
151
178
  }
152
179
  }
@@ -7,7 +7,7 @@
7
7
  <WorkItemButton
8
8
  @workItemId={{this.workItem}}
9
9
  @mutation={{this.action}}
10
- @label={{@field.question.label}}
10
+ @label={{@field.question.raw.label}}
11
11
  @disabled={{and (not-eq isValid null) (not isValid)}}
12
12
  @color={{this.color}}
13
13
  @beforeMutate={{fn this.beforeMutate validate}}
@@ -31,34 +31,36 @@ export default class CfFieldInputActionButtonComponent extends Component {
31
31
  }
32
32
 
33
33
  get action() {
34
- return this.args.field.question.action.toLowerCase();
34
+ return this.args.field.question.raw.action.toLowerCase();
35
35
  }
36
36
 
37
37
  get color() {
38
- return this.args.field.question.color.toLowerCase();
38
+ return this.args.field.question.raw.color.toLowerCase();
39
39
  }
40
40
 
41
41
  get type() {
42
- return this.args.field.question.action === "COMPLETE" ? "submit" : "button";
42
+ return this.args.field.question.raw.action === "COMPLETE"
43
+ ? "submit"
44
+ : "button";
43
45
  }
44
46
 
45
47
  get validateOnEnter() {
46
48
  return (
47
- this.args.field.question.action === "COMPLETE" &&
48
- this.args.field.question.validateOnEnter
49
+ this.args.field.question.raw.action === "COMPLETE" &&
50
+ this.args.field.question.raw.validateOnEnter
49
51
  );
50
52
  }
51
53
 
52
54
  @action
53
55
  async beforeMutate(validateFn) {
54
56
  if (
55
- this.args.field.question.action === "COMPLETE" &&
57
+ this.args.field.question.raw.action === "COMPLETE" &&
56
58
  !(await validateFn())
57
59
  ) {
58
60
  return false;
59
61
  }
60
62
 
61
- const confirmText = this.args.field.question.infoText;
63
+ const confirmText = this.args.field.question.raw.infoText;
62
64
 
63
65
  return !confirmText || confirm(confirmText);
64
66
  }
@@ -4,7 +4,7 @@
4
4
  <input
5
5
  class="uk-checkbox uk-margin-small-right"
6
6
  type="checkbox"
7
- name="{{@field.pk}}:Option:{{option.slug}}"
7
+ name={{@field.pk}}
8
8
  value={{option.slug}}
9
9
  checked={{includes option.slug @field.answer.value}}
10
10
  disabled={{@disabled}}
@@ -1,28 +1,6 @@
1
1
  import { action } from "@ember/object";
2
2
  import Component from "@glimmer/component";
3
3
 
4
- /**
5
- * Function to extract the option slug out of an input element. This is needed
6
- * since IE11 does not properly set the `value` attribute but sets the value to
7
- * `on` if checked and `off` if not. So for all sane browsers we use the
8
- * `value` attribute but for IE11 we use the appended option slug to the field
9
- * id as the input elements `name` property.
10
- *
11
- * E.g: An element with
12
- * `name="Document:xxx-xxx:Question:some-question:Option:some-option"` will
13
- * return `some-option`.
14
- *
15
- * For further information about this bug see
16
- * https://github.com/emberjs/ember.js/issues/15203
17
- *
18
- * @function getSlug
19
- * @param {Element} element The html input element
20
- * @return {String} The option slug
21
- */
22
- const getSlug = ({ value, name }) => {
23
- return ["on", "off"].includes(value) ? name.split(":").pop() : value;
24
- };
25
-
26
4
  /**
27
5
  * Input component for the checkbox question type
28
6
  *
@@ -37,13 +15,15 @@ export default class CfFieldInputCheckboxComponent extends Component {
37
15
  * @method update
38
16
  */
39
17
  @action
40
- update({ target }) {
41
- const checkedBoxes = [
42
- ...target.parentNode.parentNode.querySelectorAll(
43
- "input[type=checkbox]:checked"
44
- ),
45
- ];
18
+ update({ target: { value, checked } }) {
19
+ const valueSet = new Set(this.args.field.value);
20
+
21
+ if (checked) {
22
+ valueSet.add(value);
23
+ } else {
24
+ valueSet.delete(value);
25
+ }
46
26
 
47
- this.args.onSave([...new Set(checkedBoxes.map(getSlug))]);
27
+ this.args.onSave([...valueSet]);
48
28
  }
49
29
  }
@@ -1,7 +1,8 @@
1
- import { action, set } from "@ember/object";
1
+ import { action } from "@ember/object";
2
2
  import { inject as service } from "@ember/service";
3
3
  import Component from "@glimmer/component";
4
4
  import { queryManager } from "ember-apollo-client";
5
+ import fetch from "fetch";
5
6
 
6
7
  import removeAnswerMutation from "@projectcaluma/ember-form/gql/mutations/remove-answer.graphql";
7
8
  import getFileAnswerInfoQuery from "@projectcaluma/ember-form/gql/queries/fileanswer-info.graphql";
@@ -24,7 +25,7 @@ export default class CfFieldInputFileComponent extends Component {
24
25
  const { downloadUrl } = await this.apollo.watchQuery(
25
26
  {
26
27
  query: getFileAnswerInfoQuery,
27
- variables: { id: this.args.field.answer.id },
28
+ variables: { id: this.args.field.answer.raw.id },
28
29
  fetchPolicy: "cache-and-network",
29
30
  },
30
31
  "node.fileValue"
@@ -55,15 +56,13 @@ export default class CfFieldInputFileComponent extends Component {
55
56
  throw new Error();
56
57
  }
57
58
 
58
- // eslint-disable-next-line ember/classic-decorator-no-classic-methods
59
- set(this.args.field.answer, "value", {
59
+ this.args.field.answer.value = {
60
60
  name: file.name,
61
61
  downloadUrl: fileValue.downloadUrl,
62
- });
62
+ };
63
63
  } catch (error) {
64
64
  await this.args.onSave(null);
65
- // eslint-disable-next-line ember/classic-decorator-no-classic-methods
66
- set(this.args.field, "_errors", [{ type: "uploadFailed" }]);
65
+ this.args.field._errors = [{ type: "uploadFailed" }];
67
66
  } finally {
68
67
  // eslint-disable-next-line require-atomic-updates
69
68
  target.value = "";
@@ -84,7 +83,7 @@ export default class CfFieldInputFileComponent extends Component {
84
83
 
85
84
  await this.args.onSave(null);
86
85
  } catch (error) {
87
- set(this.args.field, "_errors", [{ type: "deleteFailed" }]);
86
+ this.args.field._errors = [{ type: "deleteFailed" }];
88
87
  }
89
88
  }
90
89
  }
@@ -8,7 +8,7 @@
8
8
  name={{@field.pk}}
9
9
  id={{@field.pk}}
10
10
  value={{@field.value}}
11
- min={{@field.question.floatMinValue}}
12
- max={{@field.question.floatMaxValue}}
11
+ min={{@field.question.raw.floatMinValue}}
12
+ max={{@field.question.raw.floatMaxValue}}
13
13
  {{on "input" this.input}}
14
14
  />
@@ -7,9 +7,9 @@
7
7
  name={{@field.pk}}
8
8
  id={{@field.pk}}
9
9
  value={{@field.answer.value}}
10
- min={{@field.question.integerMinValue}}
11
- max={{@field.question.integerMaxValue}}
12
- placeholder={{@field.question.placeholder}}
10
+ min={{@field.question.raw.integerMinValue}}
11
+ max={{@field.question.raw.integerMaxValue}}
12
+ placeholder={{@field.question.raw.placeholder}}
13
13
  readonly={{@disabled}}
14
14
  {{on "input" this.input}}
15
15
  />
@@ -4,7 +4,7 @@ import { inject as service } from "@ember/service";
4
4
  import Component from "@glimmer/component";
5
5
  import { tracked } from "@glimmer/tracking";
6
6
  import { queryManager } from "ember-apollo-client";
7
- import { dropTask } from "ember-concurrency-decorators";
7
+ import { dropTask } from "ember-concurrency";
8
8
  import UIkit from "uikit";
9
9
 
10
10
  import removeDocumentMutation from "@projectcaluma/ember-form/gql/mutations/remove-document.graphql";
@@ -37,13 +37,13 @@ export default class CfFieldInputTableComponent extends Component {
37
37
  }
38
38
 
39
39
  get questions() {
40
- return this.args.field.question.rowForm.questions.edges.map(
40
+ return this.args.field.question.raw.rowForm.questions.edges.map(
41
41
  (edge) => edge.node
42
42
  );
43
43
  }
44
44
 
45
45
  get columns() {
46
- const config = this.args.field.question.meta.columnsToDisplay;
46
+ const config = this.args.field.question.raw.meta.columnsToDisplay;
47
47
 
48
48
  if (config?.length) {
49
49
  return this.questions.filter((question) =>
@@ -59,17 +59,19 @@ export default class CfFieldInputTableComponent extends Component {
59
59
  const raw = yield this.apollo.mutate(
60
60
  {
61
61
  mutation: saveDocumentMutation,
62
- variables: { input: { form: this.args.field.question.rowForm.slug } },
62
+ variables: {
63
+ input: { form: this.args.field.question.raw.rowForm.slug },
64
+ },
63
65
  },
64
66
  "saveDocument.document"
65
67
  );
66
68
 
67
- const newDocument = getOwner(this)
68
- .factoryFor("caluma-model:document")
69
- .create({
70
- raw: this.parseDocument(raw),
71
- parentDocument: this.args.field.document,
72
- });
69
+ const owner = getOwner(this);
70
+ const newDocument = new (owner.factoryFor("caluma-model:document").class)({
71
+ raw: this.parseDocument(raw),
72
+ parentDocument: this.args.field.document,
73
+ owner,
74
+ });
73
75
 
74
76
  this.documentToEditIsNew = true;
75
77
  this.documentToEdit = newDocument;
@@ -6,9 +6,9 @@
6
6
  name={{@field.pk}}
7
7
  id={{@field.pk}}
8
8
  value={{@field.answer.value}}
9
- placeholder={{@field.question.placeholder}}
9
+ placeholder={{@field.question.raw.placeholder}}
10
10
  readonly={{@disabled}}
11
- minlength={{@field.question.textMinLength}}
12
- maxlength={{@field.question.textMaxLength}}
11
+ minlength={{@field.question.raw.textMinLength}}
12
+ maxlength={{@field.question.raw.textMaxLength}}
13
13
  {{on "input" this.input}}
14
14
  />
@@ -4,9 +4,9 @@
4
4
  {{if @disabled 'uk-disabled'}}"
5
5
  name={{@field.pk}}
6
6
  id={{@field.pk}}
7
- placeholder={{@field.question.placeholder}}
8
- minlength={{@field.question.textareaMinLength}}
9
- maxlength={{@field.question.textareaMaxLength}}
7
+ placeholder={{@field.question.raw.placeholder}}
8
+ minlength={{@field.question.raw.textareaMinLength}}
9
+ maxlength={{@field.question.raw.textareaMaxLength}}
10
10
  readonly={{@disabled}}
11
11
  {{on "input" this.input}}
12
12
  >{{@field.answer.value}}</textarea>
@@ -22,7 +22,7 @@ export default class CfFieldInputComponent extends Component {
22
22
  * @accessor
23
23
  */
24
24
  get type() {
25
- const typename = this.args.field?.question.__typename;
25
+ const typename = this.args.field?.question.raw.__typename;
26
26
 
27
27
  return (
28
28
  typename &&
@@ -1,6 +1,6 @@
1
1
  <label class="uk-form-label" for={{@field.pk}}>
2
2
  <span class="uk-text-bold">
3
- {{@field.question.label}}
3
+ {{@field.question.raw.label}}
4
4
  </span>
5
5
 
6
6
  {{#if this.optional}}
@@ -11,7 +11,7 @@ export default class CfFieldValueComponent extends Component {
11
11
  get value() {
12
12
  const field = this.args.field;
13
13
 
14
- switch (field.question.__typename) {
14
+ switch (field.questionType) {
15
15
  case "ChoiceQuestion":
16
16
  case "DynamicChoiceQuestion": {
17
17
  return field.selected;
@@ -23,8 +23,8 @@ export default class CfFieldValueComponent extends Component {
23
23
  case "FileQuestion": {
24
24
  const answerValue = field.answer.value;
25
25
  return {
26
- fileAnswerId: answerValue && field.answer.id,
27
- label: answerValue && answerValue.name,
26
+ fileAnswerId: answerValue && field.answer.raw.id,
27
+ label: answerValue?.name,
28
28
  };
29
29
  }
30
30
  case "DateQuestion": {
@@ -34,22 +34,17 @@ export default class CfFieldValueComponent extends Component {
34
34
  }
35
35
 
36
36
  default:
37
- return {
38
- label: field.answer.value,
39
- };
37
+ return { label: field.answer.value };
40
38
  }
41
39
  }
42
40
 
43
41
  @action
44
- async download(fileAnswerId) {
42
+ async download(id) {
45
43
  const { downloadUrl } = await this.apollo.query(
46
- {
47
- query: getFileAnswerInfoQuery,
48
- variables: { id: fileAnswerId },
49
- fetchPolicy: "cache-and-network",
50
- },
44
+ { query: getFileAnswerInfoQuery, variables: { id } },
51
45
  "node.fileValue"
52
46
  );
47
+
53
48
  window.open(downloadUrl, "_blank");
54
49
  }
55
50
  }
@@ -16,8 +16,8 @@
16
16
  {{/let}}
17
17
  </div>
18
18
 
19
- {{#if (and @field.question.infoText this.infoTextVisible)}}
20
- <CfField::info @text={{@field.question.infoText}} />
19
+ {{#if (and @field.question.raw.infoText this.infoTextVisible)}}
20
+ <CfField::info @text={{@field.question.raw.infoText}} />
21
21
  {{/if}}
22
22
 
23
23
  {{#if this.saveIndicatorVisible}}
@@ -1,8 +1,7 @@
1
1
  import { getOwner } from "@ember/application";
2
2
  import { set } from "@ember/object";
3
3
  import Component from "@glimmer/component";
4
- import { timeout } from "ember-concurrency";
5
- import { restartableTask } from "ember-concurrency-decorators";
4
+ import { timeout, restartableTask } from "ember-concurrency";
6
5
 
7
6
  import { hasQuestionType } from "@projectcaluma/ember-core/helpers/has-question-type";
8
7
 
@@ -28,7 +27,7 @@ export default class CfFieldComponent extends Component {
28
27
 
29
28
  get labelVisible() {
30
29
  return (
31
- !this.args.field?.question.meta.hideLabel &&
30
+ !this.args.field?.question.raw.meta.hideLabel &&
32
31
  !hasQuestionType(this.args.field?.question, "static", "action-button")
33
32
  );
34
33
  }
@@ -1,6 +1,6 @@
1
1
  import { action } from "@ember/object";
2
2
  import Component from "@glimmer/component";
3
- import { restartableTask } from "ember-concurrency-decorators";
3
+ import { restartableTask } from "ember-concurrency";
4
4
 
5
5
  /**
6
6
  * Component to check the validity of a document
@@ -0,0 +1,50 @@
1
+ import Helper from "@ember/component/helper";
2
+ import { warn } from "@ember/debug";
3
+ import { inject as service } from "@ember/service";
4
+
5
+ /**
6
+ * Helper for getting the right widget.
7
+ *
8
+ * This helper expects n objects as positional parameters. It checks if the
9
+ * object has a widget override in it's metadata. If one exists it checks if
10
+ * said widget was registered in the caluma options service and then returns
11
+ * the widget name. If it doesn't have a valid widget, the next object will be
12
+ * checked. If no object returns a valid widget, the passed default widget will
13
+ * be used.
14
+ *
15
+ * ```hbs
16
+ * {{component (get-widget field.question someobject default="cf-form") foo=bar}}
17
+ * ```
18
+ *
19
+ * @function getWidget
20
+ * @param {Array} params
21
+ * @param {Object} [options]
22
+ * @param {String} [options.default]
23
+ */
24
+ export default class GetWidgetHelper extends Helper {
25
+ @service calumaOptions;
26
+
27
+ compute(params, { default: defaultWidget = "cf-field/input" }) {
28
+ for (const obj of params) {
29
+ const widget = obj?.raw?.meta?.widgetOverride;
30
+ if (!widget) {
31
+ continue;
32
+ }
33
+ const override =
34
+ widget &&
35
+ this.calumaOptions
36
+ .getComponentOverrides()
37
+ .find(({ component }) => component === widget);
38
+
39
+ warn(
40
+ `Widget override "${widget}" is not registered. Please register it by calling \`calumaOptions.registerComponentOverride\``,
41
+ override,
42
+ { id: "ember-caluma.unregistered-override" }
43
+ );
44
+
45
+ if (override) return widget;
46
+ }
47
+
48
+ return defaultWidget;
49
+ }
50
+ }