@projectcaluma/ember-form 10.0.3 → 11.0.0-beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }